From af68030fcf0c266300feb2c100149ecadef7d364 Mon Sep 17 00:00:00 2001 From: xue <> Date: Sun, 16 Jul 2006 01:50:23 +0000 Subject: Merge from 3.0 branch till 1264. --- .gitattributes | 19 +- HISTORY | 372 ++++----- buildscripts/texbuilder/Page2Tex.php | 286 +++++++ demos/blog/protected/Pages/SearchPost.php | 2 +- demos/quickstart/protected/controls/TopicList.tpl | 2 +- .../protected/pages/Advanced/Assets.page | 2 +- .../protected/pages/Advanced/Collections.page | 2 +- .../quickstart/protected/pages/Advanced/Error.page | 4 +- .../quickstart/protected/pages/Advanced/I18N.page | 52 +- .../protected/pages/Advanced/Logging.page | 4 +- .../protected/pages/Advanced/Scripts.page | 178 ++--- .../protected/pages/Advanced/Scripts1.page | 156 ++-- .../protected/pages/Advanced/Scripts2.page | 144 ++-- .../protected/pages/Advanced/Scripts3.page | 22 +- .../protected/pages/Advanced/Security.page | 8 +- .../quickstart/protected/pages/Advanced/State.page | 2 +- .../protected/pages/Advanced/Themes.page | 8 +- demos/quickstart/protected/pages/Comments.page | 46 -- demos/quickstart/protected/pages/Comments.php | 76 -- .../protected/pages/Controls/ClientScript.page | 36 +- .../protected/pages/Controls/DataGrid.page | 10 +- .../protected/pages/Controls/DataList.page | 2 +- .../protected/pages/Controls/DatePicker.page | 34 +- .../protected/pages/Controls/HtmlArea.page | 4 +- .../protected/pages/Controls/JavascriptLogger.page | 4 +- .../protected/pages/Controls/NewControl.page | 6 +- .../quickstart/protected/pages/Controls/Panel.page | 2 +- .../protected/pages/Controls/Repeater.page | 2 +- .../protected/pages/Controls/Validation.page | 4 +- .../protected/pages/Controls/Wizard.page | 6 +- .../pages/GettingStarted/Introduction.page | 4 +- demos/quickstart/protected/pages/Search.page | 6 +- framework/Web/UI/TControl.php | 22 + framework/Web/UI/TPage.php | 1 + framework/Web/UI/WebControls/TCheckBox.php | 2 +- framework/Web/UI/WebControls/TClientScript.php | 42 -- framework/Web/UI/WebControls/TCustomValidator.php | 10 + framework/Web/UI/WebControls/TRadioButton.php | 66 +- framework/Web/UI/WebControls/TTextHighlighter.php | 2 +- .../tickets/protected/pages/Ticket284.page | 14 + .../tickets/protected/pages/Ticket284.php | 11 + .../tickets/protected/pages/Ticket284Component.php | 94 +++ .../tickets/protected/pages/Ticket284Component.tpl | 94 +++ .../tickets/tests/Ticket284TestCase.php | 14 + tests/test_tools/simpletest/CHANGELOG | 2 +- .../simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE | 119 ++- tests/test_tools/simpletest/LICENSE | 507 ++++++++++++- tests/test_tools/simpletest/README | 22 +- tests/test_tools/simpletest/VERSION | 2 +- tests/test_tools/simpletest/authentication.php | 23 +- tests/test_tools/simpletest/browser.php | 397 +++++----- tests/test_tools/simpletest/collector.php | 115 +++ tests/test_tools/simpletest/compatibility.php | 184 +++++ tests/test_tools/simpletest/cookies.php | 380 ++++++++++ tests/test_tools/simpletest/detached.php | 96 +++ .../docs/en/authentication_documentation.html | 48 +- .../simpletest/docs/en/browser_documentation.html | 27 +- .../docs/en/expectation_documentation.html | 40 +- .../docs/en/form_testing_documentation.html | 13 +- .../docs/en/group_test_documentation.html | 5 +- tests/test_tools/simpletest/docs/en/index.html | 48 +- .../docs/en/mock_objects_documentation.html | 272 +++---- tests/test_tools/simpletest/docs/en/overview.html | 154 ++-- .../docs/en/partial_mocks_documentation.html | 3 - .../simpletest/docs/en/reporter_documentation.html | 3 - .../docs/en/unit_test_documentation.html | 19 +- .../docs/en/web_tester_documentation.html | 31 +- .../docs/fr/authentication_documentation.html | 264 ------- .../simpletest/docs/fr/browser_documentation.html | 329 -------- tests/test_tools/simpletest/docs/fr/docs.css | 84 --- .../docs/fr/expectation_documentation.html | 263 ------- .../docs/fr/form_testing_documentation.html | 235 ------ .../docs/fr/group_test_documentation.html | 288 ------- tests/test_tools/simpletest/docs/fr/index.html | 343 --------- .../docs/fr/mock_objects_documentation.html | 492 ------------ tests/test_tools/simpletest/docs/fr/overview.html | 294 -------- .../docs/fr/partial_mocks_documentation.html | 333 --------- .../simpletest/docs/fr/reporter_documentation.html | 386 ---------- .../docs/fr/server_stubs_documentation.html | 279 ------- .../docs/fr/unit_test_documentation.html | 306 -------- .../docs/fr/web_tester_documentation.html | 397 ---------- tests/test_tools/simpletest/dumper.php | 54 +- tests/test_tools/simpletest/encoding.php | 493 ++++++++++-- tests/test_tools/simpletest/errors.php | 63 +- tests/test_tools/simpletest/exceptions.php | 46 ++ tests/test_tools/simpletest/expectation.php | 234 +++++- .../simpletest/extensions/pear_test_case.php | 199 ----- .../simpletest/extensions/phpunit_test_case.php | 108 --- tests/test_tools/simpletest/form.php | 395 ++-------- tests/test_tools/simpletest/frames.php | 241 ++---- tests/test_tools/simpletest/http.php | 365 ++------- tests/test_tools/simpletest/invoker.php | 139 ++++ tests/test_tools/simpletest/mock_objects.php | 832 ++++++++++----------- tests/test_tools/simpletest/page.php | 584 ++++++++------- tests/test_tools/simpletest/parser.php | 213 +++--- tests/test_tools/simpletest/reflection_php4.php | 115 +++ tests/test_tools/simpletest/reflection_php5.php | 275 +++++++ tests/test_tools/simpletest/remote.php | 8 +- tests/test_tools/simpletest/reporter.php | 178 ++++- tests/test_tools/simpletest/scorer.php | 507 +++++++++++-- tests/test_tools/simpletest/selector.php | 133 ++++ tests/test_tools/simpletest/shell_tester.php | 63 +- tests/test_tools/simpletest/simpletest.php | 282 +++++++ tests/test_tools/simpletest/socket.php | 28 +- tests/test_tools/simpletest/tag.php | 553 +++++++++----- tests/test_tools/simpletest/test_case.php | 684 +++++++++++++++++ tests/test_tools/simpletest/unit_tester.php | 182 +++-- tests/test_tools/simpletest/url.php | 105 +-- tests/test_tools/simpletest/user_agent.php | 297 ++------ tests/test_tools/simpletest/web_tester.php | 491 +++++++----- tests/test_tools/simpletest/xml.php | 107 ++- 111 files changed, 8130 insertions(+), 8524 deletions(-) create mode 100644 buildscripts/texbuilder/Page2Tex.php delete mode 100644 demos/quickstart/protected/pages/Comments.page delete mode 100644 demos/quickstart/protected/pages/Comments.php create mode 100644 tests/FunctionalTests/tickets/protected/pages/Ticket284.page create mode 100644 tests/FunctionalTests/tickets/protected/pages/Ticket284.php create mode 100644 tests/FunctionalTests/tickets/protected/pages/Ticket284Component.php create mode 100644 tests/FunctionalTests/tickets/protected/pages/Ticket284Component.tpl create mode 100644 tests/FunctionalTests/tickets/tests/Ticket284TestCase.php create mode 100644 tests/test_tools/simpletest/collector.php create mode 100644 tests/test_tools/simpletest/compatibility.php create mode 100644 tests/test_tools/simpletest/cookies.php create mode 100644 tests/test_tools/simpletest/detached.php delete mode 100755 tests/test_tools/simpletest/docs/fr/authentication_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/browser_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/docs.css delete mode 100755 tests/test_tools/simpletest/docs/fr/expectation_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/form_testing_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/group_test_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/index.html delete mode 100755 tests/test_tools/simpletest/docs/fr/mock_objects_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/overview.html delete mode 100755 tests/test_tools/simpletest/docs/fr/partial_mocks_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/reporter_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/server_stubs_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/unit_test_documentation.html delete mode 100755 tests/test_tools/simpletest/docs/fr/web_tester_documentation.html create mode 100644 tests/test_tools/simpletest/exceptions.php delete mode 100755 tests/test_tools/simpletest/extensions/pear_test_case.php delete mode 100755 tests/test_tools/simpletest/extensions/phpunit_test_case.php create mode 100644 tests/test_tools/simpletest/invoker.php create mode 100644 tests/test_tools/simpletest/reflection_php4.php create mode 100644 tests/test_tools/simpletest/reflection_php5.php create mode 100644 tests/test_tools/simpletest/selector.php create mode 100644 tests/test_tools/simpletest/simpletest.php create mode 100644 tests/test_tools/simpletest/test_case.php diff --git a/.gitattributes b/.gitattributes index 993b0cba..3127d88f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -587,6 +587,7 @@ buildscripts/phing/tasks/XmlLintTask.php -text buildscripts/phing/tasks/ZendCodeAnalyzerTask.php -text buildscripts/phpbuilder/build.php -text buildscripts/setup.php -text +buildscripts/texbuilder/Page2Tex.php -text buildscripts/texbuilder/quickstart/build.php -text buildscripts/texbuilder/quickstart/pages.php -text buildscripts/texbuilder/quickstart/quickstart.tex -text @@ -803,8 +804,6 @@ demos/quickstart/protected/pages/Advanced/mastercontent.gif -text demos/quickstart/protected/pages/Advanced/mastercontent.vsd -text demos/quickstart/protected/pages/Advanced/pcrelation.gif -text demos/quickstart/protected/pages/Advanced/pcrelation.vsd -text -demos/quickstart/protected/pages/Comments.page -text -demos/quickstart/protected/pages/Comments.php -text demos/quickstart/protected/pages/Configurations/AppConfig.page -text demos/quickstart/protected/pages/Configurations/Overview.page -text demos/quickstart/protected/pages/Configurations/PageConfig.page -text @@ -1863,6 +1862,10 @@ tests/FunctionalTests/tickets/protected/pages/Ticket239.php -text tests/FunctionalTests/tickets/protected/pages/Ticket27.page -text tests/FunctionalTests/tickets/protected/pages/Ticket28.page -text tests/FunctionalTests/tickets/protected/pages/Ticket28.php -text +tests/FunctionalTests/tickets/protected/pages/Ticket284.page -text +tests/FunctionalTests/tickets/protected/pages/Ticket284.php -text +tests/FunctionalTests/tickets/protected/pages/Ticket284Component.php -text +tests/FunctionalTests/tickets/protected/pages/Ticket284Component.tpl -text tests/FunctionalTests/tickets/protected/pages/Ticket54.page -text tests/FunctionalTests/tickets/protected/pages/Ticket54Master.php -text tests/FunctionalTests/tickets/protected/pages/Ticket54Master.tpl -text @@ -1880,6 +1883,7 @@ tests/FunctionalTests/tickets/tests/Ticket191TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket21TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket239TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket27TestCase.php -text +tests/FunctionalTests/tickets/tests/Ticket284TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket28TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket54TestCase.php -text tests/FunctionalTests/tickets/tests/Ticket72TestCase.php -text @@ -1926,6 +1930,17 @@ tests/FunctionalTests/validators/tests/RegExpValidatorTestCase.php -text tests/FunctionalTests/validators/tests/RequiredFieldTestCase.php -text tests/FunctionalTests/validators/tests/RequiredListTestCase.php -text tests/FunctionalTests/validators/tests/ValidationSummaryTestCase.php -text +tests/test_tools/simpletest/collector.php -text +tests/test_tools/simpletest/compatibility.php -text +tests/test_tools/simpletest/cookies.php -text +tests/test_tools/simpletest/detached.php -text +tests/test_tools/simpletest/exceptions.php -text +tests/test_tools/simpletest/invoker.php -text +tests/test_tools/simpletest/reflection_php4.php -text +tests/test_tools/simpletest/reflection_php5.php -text +tests/test_tools/simpletest/selector.php -text +tests/test_tools/simpletest/simpletest.php -text +tests/test_tools/simpletest/test_case.php -text tests/unit/Collections/TListTest.php -text tests/unit/Collections/TMapTest.php -text tests/unit/I18N/core/CultureInfoTest.php -text diff --git a/HISTORY b/HISTORY index fe92f7fd..93038b23 100644 --- a/HISTORY +++ b/HISTORY @@ -1,185 +1,187 @@ -Version 3.1.0 To be released -============================ -BUG: Ticket#188 - Postback js caused controls not inheritable (Qiang) -ENH: Ticket#180, #222 - Prado js files are not rendered at the beginning of the form (Qiang) -ENH: Ticket#99 - TXmlTransform control (Knut) -ENH: Ticket#117 - added support to configure subproperties in a group. (Qiang) -NEW: TOutputCache (Qiang) -NEW: TQueue (Qiang) -NEW: TSessionPageStatePersister (Qiang) -NEW: SQLMap (Wei) -NEW: TFeedService, TRssFeedDocument (Knut, Qiang) - -Version 3.0.3 August 6, 2006 -============================ -BUG: Ticket#264 - Typos in some exception throw statements (Knut) -BUG: Ticket#268 - THttpResponse.redirect() may fail for some browsers (Qiang) -BUG: TDataGrid may complain getting ItemType on a non-object if the grid is not data-bound (Qiang) -ENH: Ticket#220 - TClientScripts method to import custom javascript files (Wei) -ENH: Ticket#225 - TRadioButton::getRadioButtonsInGroup() added (Wei) -ENH: Ticket#223 - Use TRequiredFieldValidator for TRadioButtons with GroupName property (Wei) -NEW: Added TStyleSheet (Wei) - -Version 3.0.2 July 2, 2006 -========================== -BUG: Ticket#182 - List and validator controls cause problem in child classes (Qiang) -BUG: Ticket#191 - Duplicated postbacks occur when using TButton with validators (Qiang) -BUG: Ticket#207 - Validators ClientSide.OnError triggered twice (Wei) -BUG: Ticket#213 - PRADO Requirements Checker charset error (Qiang) -BUG: Ticket#227 - Enabled property doesn't works with THtmlArea (Wei) -BUG: Ticket#234 - Postback target could be out of date (Qiang) -BUG: Ticket#239 - Ondeactivate handler for the first View of MultiView is always fired (Qiang) -BUG: Ticket#244 - redirect() needs absolute URL (Qiang) -BUG: Ticket#245 - getIsSecureConnection() is not working correctly (Qiang) -BUG: Ticket#246 - TDatePicker wrong popup position in scrolled div (Wei) -BUG: Ticket#260 - Wrong value of a configuration option in setUseTransparentSessionID (Qiang) -CHG: ensureChildControls() is now invoked in TControl::initRecursive (Qiang) -CHG: Postback enabled control will always disable default client-side browser action. (Qiang) -CHG: CSS and JS files in a theme are now included in page in alphabetic order (Qiang) -ENH: Ticket#206 - Added OnValidate, OnError, OnSuccess events to validators (Qiang) -ENH: Ticket#230 - Added TDataList.EmptyTemplate property (Qiang) -ENH: Ticket#231 - Added TButton.ButtonType property to allow reset button (Qiang) -ENH: Ticket#232 - Allow <%# %> and <%= %> embedded within property values (Qiang) -ENH: Ticket#256 - Datagrid columns can now be accessed via IDs (Qiang) -ENH: Ticket#257 - OnSelectedIndexChanged event of TDataList and TDataGrid now passes the original command parameter (Qiang) -ENH: Ticket#262 - Added TCheckBox.Value property (Qiang) -ENH: TRepeater, TDataList and TDataGrid will store data indices in DataKeys if DataKeyField is not set. (Qiang) -ENH: Added TPageService.BasePageClass property (Qiang) -ENH: Added TDataGrid.EmptyTemplate property (Qiang) -ENH: Added paging feature to all TDataBoundControl-derived controls (Qiang) -ENH: ClientSide.ObserveChanges="false" to only revalidate client side validator when control changes (Wei) -NEW: Added TPager (Qiang) -NEW: Added Dreamweaver taglib extension (Stanislav, Qiang) -NEW: Prado Command line script to create a new project, see framework/prado-cli.php (Wei) - -Version 3.0.1 June 4, 2006 -========================== -BUG: Ticket#28 - OnClick does not work with Safari/KHTML (Wei) -BUG: Ticket#37 - Changes of config files do not trigger cache update (Qiang) -BUG: Ticket#44 - THtmlArea (tiny_mce) not working on some systems (Qiang) -BUG: Ticket#162 - Missing currency sign in TNumberFormat if Value is 0 (Wei) -BUG: Ticket#167 - TSecurityManager issues warning when trying to encrypt/decrypt strings (Qiang) -BUG: Ticket#169 - Validation of subclass of THtmlArea/TDatePicker fails (Wei) -BUG: Ticket#179 - CGI incompatibility causing clientscripts.php failure (Qiang) -BUG: Ticket#181 - Unable to change Content-Type in response header if charset is not set (Qiang) -BUG: Ticket#200 - onClick javascript event triggered twice on CheckBox label (Qiang) -BUG: newly created controls during postbacks might get their initial states reset during loadStateRecursive. (Qiang) -ENH: Ticket#150 - TDataGrid and TDataList now render table section tags (Qiang) -ENH: Ticket#152 - constituent parts of TWizard are exposed (Qiang) -ENH: Ticket#184 - added TUserManager.Users and Roles properties (Qiang) -ENH: Ticket#197 - sanity check for datagrid header and footer (Qiang) -ENH: added sanity check to calling event handlers (Qiang) -ENH: added search for quickstart tutorials (Wei) -ENH: added support to property tags for template owner control (Qiang) -ENH: added Bulgarian requirement checker messages (StanProg) -ENH: added TTheme.BaseUrl and TTheme.BasePath property (Qiang) -ENH: added TListControl.SelectedValues property (Qiang) -ENH: added TThemeManager.AvailableThemes property (Qiang) -ENH: refactored TUserManager and TAuthManager so that they are easier to be extended (Qiang) -ENH: template syntax now supports setting event handler via subproperties (Qiang) -CHG: Ticket#151 - URL format is modified to handle empty GET values (Qiang) -CHG: Ticket#153 - TAssetManager now ignores .svn directories (Qiang) -CHG: Changed TControl::onBubbleEvent() to TControl::bubbleEvent() (Qiang) -NEW: TTableHeaderRow, TTableFooterRow and table section support (Qiang) -NEW: TCompositeControl (Qiang) -NEW: TTextProcessor (Qiang) -NEW: TMarkdown (Wei) -NEW: Blog demo (Qiang) - -Version 3.0.0 May 1, 2006 -========================= -BUG: Ticket#68 - 2 TButtons with THtmlArea causes second button to fail to function (Wei) -BUG: Ticket#70 - Javascript for TDataTypeValidator added (Wei) -BUG: Ticket#131 - TImageMap and TLinkButton continue to postback even client validator fails (Wei) -BUG: Ticket#135 - TBrowserLogRoute reports wrong timings (Qiang) -BUG: Ticket#137 - The JavasciptLogger does not work (Qiang) -BUG: Ticket#138 - missing file in TDataGrid (Qiang) -BUG: Ticket#139 - TThemeManager::setBasePath() should understand an alias path (Qiang) -BUG: Non-control components can now use expressions in their properties (Qiang) -BUG: TControl.Visible did not make use of overriden getVisible() (Qiang) -BUG: TWizard did not stop navigation upon a validation failure (Qiang) -BUG: NumberFormat will now zero-fill numbers base on the pattern. (Wei) -ENH: TButton, TImageButton and TLinkButton now implement IButtonControl interface (Qiang) -ENH: TResponse::writeFile takes three additional parameters to allow sending memory data (Qiang) -ENH: TButtonColumn can now be a column of image buttons (Qiang) -ENH: TLiteral will display body content if Text is empty (Qiang) -ENH: Format string in classes extending TDataGridColumn can now evaluate an expression (Qiang) -ENH: Format string in classes extending TListControl can now evaluate an expression (Qiang) -ENH: Added THttpResponse::reload() (Qiang) -ENH: Custom visual effects can be added to client-side validators via ClientSide property in validators. (Wei) -ENH: TJavascript::encode() allows raw javascript code when string begins with "javascript:" (Wei) -ENH: Update TinyMCE to 2.0.5.1 -CHG: Rewrote client-side javascript validators, check your client-side validation behaviour (Wei) -CHG: Updated the javascript Prototype library, a few utilties functions REMOVED, may break your existing javascript code. (Wei) -CHG: Build javascript without compression, only comments are removed. (Wei) -CHG: TDatePicker's date can be set using Date property, it value must be in same format as DateFormat, TimeStamp must be set as integer (wei) -CHG: TSimpleDateFormatter::parse() now return an integer or null on parse error (Wei) -CHG: TControl::createControls() is changed to public. (Qiang) -CHG: Template comment tag is changed from to (Qiang) -NEW: TListControlValidator (Wei) -NEW: TClientScript (Wei) - - -Version 3.0RC2 April 16, 2006 -============================= -BUG: Ticket#54 - recursive reverse() definition (Wei) -BUG: Ticket#93 - ValidationGroup not working in TImageMap for js validator (Wei) -BUG: Ticket#97 - Invalid return type value for TSimpleDateFormatter::parse (Wei) -BUG: Ticket#118 - Variables that may not have been initialized (Qiang) -BUG: Ticket#121 - OnClick don't fire with TImageButton and TRequiredFieldValidator (Wei) -BUG: Ticket#129 - TRadioButtonList in TWizard step does not postback correctly (Qiang) -CHG: Moved localize() into PradoBase (Qiang) -CHG: List controls now use array keys as list item values even if the array is integer-indexed (Qiang) -CHG: THttpUtility::htmlEncode and htmlDecode now do not deal with & (Qiang) -CHG: Expressions appeared in a template are evaluated in PreRender stage (Qiang) -CHG: The context of expressions appeared in a template is changed to the template control (Qiang) -ENH: Optimized the representation and evaluation of template expressions (Qiang) -ENH: Added Raw layout to TDataList (Qiang) -ENH: Added TRepeater.DataKeys and TRepeater.DataKeyField (Qiang) - -Version 3.0RC1 April 5, 2006 -============================ -BUG: Ticket#85 - Undefined TDataGrid::setSelectedIndex (Qiang) -BUG: Ticket#87 - Typo in IDbConnection (Qiang) -BUG: Ticket#88 - Unclosed HTML tag in TDatePicker and TColorPicker (Qiang) -BUG: Ticket#89 - TInlineFrame::ScrollBars wrong behaviour expected (Qiang) -BUG: Ticket#95 - Typo in TTemplateControl::registerContentPlaceHolder (Qiang) -BUG: Ticket#103 - Typo in TStyle::setVerticalAlign (Qiang) -BUG: Ticket#107 - ListControls not respect parent disabling (Qiang) -BUG: SF#1432624 - Incorrect documentation about caching expiry (Qiang) -BUG: SF#1446846 - Typo in THead (Qiang) -BUG: THttpSession fails when user storage module is used (Qiang) -CHG: TTextHighlighter.EnableCopyCode defaults to false (Qiang) -CHG: Reorganized quickstart tutorial demo, added new sections (Qiang) -CHG: Reorganized folders under framework (Qiang) -CHG: Modified THtmlArea default toolbar and size. (Qiang) -CHG: Pagers in TDataGrid are now enclosed within panels (Qiang) -ENH: Ticket#92 - Support for user exception message file (Qiang) -ENH: Ticket#106 - Support for validation on THiddenField (Qiang) -ENH: Ticket#110 - Support for TVarDump with syntax highlight (Qiang) -ENH: TDataFieldAccessor can access public class variables (Qiang) -ENH: TPhpErrorException now shows the actual error lines (Qiang) -ENH: Refactored cache classes with support for cache dependency (Qiang) -NEW: TStack component (Qiang) -NEW: TImageMap control (Qiang) -NEW: TWizard control (Qiang) -NEW: TVarDumper and PradoBase::varDump() (Qiang) -NEW: TComponentReflection (Qiang) -NEW: TParameterModule (Qiang) -NEW: TPropelLogRoute (Jason) - -Version 3.0b March 6, 2006 -========================== -BUG: fixed many -CHG: event names must be prefixed with 'On' (Qiang) -CHG: values of properties whose name ends with 'Template' are parsed directly by template parser (Qiang) -ENH: template parser reports exact error location (Qiang) -ENH: cookie HMAC check (Qiang) -NEW: TInlineFrame (Jason) -NEW: TAPCCache (Alban) -NEW: TColorPicker, TDatePicker, TRatingList, TAdodbProvider, TCreoleProvider (Wei) -NEW: TMultiView, TView, TControlAdapter, TWebControlAdapter, TPagedList, TAttributeCollection (Qiang) - -Version 3.0a January 18, 2006 -============================= -Starting, main feaures ready (Qiang) +Version 3.1.0 To be released +============================ +BUG: Ticket#188 - Postback js caused controls not inheritable (Qiang) +ENH: Ticket#180, #222 - Prado js files are not rendered at the beginning of the form (Qiang) +ENH: Ticket#99 - TXmlTransform control (Knut) +ENH: Ticket#117 - added support to configure subproperties in a group. (Qiang) +NEW: TOutputCache (Qiang) +NEW: TQueue (Qiang) +NEW: TSessionPageStatePersister (Qiang) +NEW: SQLMap (Wei) +NEW: TFeedService, TRssFeedDocument (Knut, Qiang) + +Version 3.0.3 August 6, 2006 +============================ +BUG: Ticket#264 - Typos in some exception throw statements (Knut) +BUG: Ticket#268 - THttpResponse.redirect() may fail for some browsers (Qiang) +BUG: TDataGrid may complain getting ItemType on a non-object if the grid is not data-bound (Qiang) +BUG: TCheckBox.Value should be converted to string (Qiang) +ENH: Ticket#220 - TClientScripts method to import custom javascript files (Wei) +ENH: Ticket#225 - TRadioButton::getRadioButtonsInGroup() added (Wei) +ENH: Ticket#223 - Use TRequiredFieldValidator for TRadioButtons with GroupName property (Wei) +ENH: Ticket#277 - Added TControl.CustomData property (Qiang) +NEW: Added TStyleSheet (Wei) + +Version 3.0.2 July 2, 2006 +========================== +BUG: Ticket#182 - List and validator controls cause problem in child classes (Qiang) +BUG: Ticket#191 - Duplicated postbacks occur when using TButton with validators (Qiang) +BUG: Ticket#207 - Validators ClientSide.OnError triggered twice (Wei) +BUG: Ticket#213 - PRADO Requirements Checker charset error (Qiang) +BUG: Ticket#227 - Enabled property doesn't works with THtmlArea (Wei) +BUG: Ticket#234 - Postback target could be out of date (Qiang) +BUG: Ticket#239 - Ondeactivate handler for the first View of MultiView is always fired (Qiang) +BUG: Ticket#244 - redirect() needs absolute URL (Qiang) +BUG: Ticket#245 - getIsSecureConnection() is not working correctly (Qiang) +BUG: Ticket#246 - TDatePicker wrong popup position in scrolled div (Wei) +BUG: Ticket#260 - Wrong value of a configuration option in setUseTransparentSessionID (Qiang) +CHG: ensureChildControls() is now invoked in TControl::initRecursive (Qiang) +CHG: Postback enabled control will always disable default client-side browser action. (Qiang) +CHG: CSS and JS files in a theme are now included in page in alphabetic order (Qiang) +ENH: Ticket#206 - Added OnValidate, OnError, OnSuccess events to validators (Qiang) +ENH: Ticket#230 - Added TDataList.EmptyTemplate property (Qiang) +ENH: Ticket#231 - Added TButton.ButtonType property to allow reset button (Qiang) +ENH: Ticket#232 - Allow <%# %> and <%= %> embedded within property values (Qiang) +ENH: Ticket#256 - Datagrid columns can now be accessed via IDs (Qiang) +ENH: Ticket#257 - OnSelectedIndexChanged event of TDataList and TDataGrid now passes the original command parameter (Qiang) +ENH: Ticket#262 - Added TCheckBox.Value property (Qiang) +ENH: TRepeater, TDataList and TDataGrid will store data indices in DataKeys if DataKeyField is not set. (Qiang) +ENH: Added TPageService.BasePageClass property (Qiang) +ENH: Added TDataGrid.EmptyTemplate property (Qiang) +ENH: Added paging feature to all TDataBoundControl-derived controls (Qiang) +ENH: ClientSide.ObserveChanges="false" to only revalidate client side validator when control changes (Wei) +NEW: Added TPager (Qiang) +NEW: Added Dreamweaver taglib extension (Stanislav, Qiang) +NEW: Prado Command line script to create a new project, see framework/prado-cli.php (Wei) + +Version 3.0.1 June 4, 2006 +========================== +BUG: Ticket#28 - OnClick does not work with Safari/KHTML (Wei) +BUG: Ticket#37 - Changes of config files do not trigger cache update (Qiang) +BUG: Ticket#44 - THtmlArea (tiny_mce) not working on some systems (Qiang) +BUG: Ticket#162 - Missing currency sign in TNumberFormat if Value is 0 (Wei) +BUG: Ticket#167 - TSecurityManager issues warning when trying to encrypt/decrypt strings (Qiang) +BUG: Ticket#169 - Validation of subclass of THtmlArea/TDatePicker fails (Wei) +BUG: Ticket#179 - CGI incompatibility causing clientscripts.php failure (Qiang) +BUG: Ticket#181 - Unable to change Content-Type in response header if charset is not set (Qiang) +BUG: Ticket#200 - onClick javascript event triggered twice on CheckBox label (Qiang) +BUG: newly created controls during postbacks might get their initial states reset during loadStateRecursive. (Qiang) +ENH: Ticket#150 - TDataGrid and TDataList now render table section tags (Qiang) +ENH: Ticket#152 - constituent parts of TWizard are exposed (Qiang) +ENH: Ticket#184 - added TUserManager.Users and Roles properties (Qiang) +ENH: Ticket#197 - sanity check for datagrid header and footer (Qiang) +ENH: added sanity check to calling event handlers (Qiang) +ENH: added search for quickstart tutorials (Wei) +ENH: added support to property tags for template owner control (Qiang) +ENH: added Bulgarian requirement checker messages (StanProg) +ENH: added TTheme.BaseUrl and TTheme.BasePath property (Qiang) +ENH: added TListControl.SelectedValues property (Qiang) +ENH: added TThemeManager.AvailableThemes property (Qiang) +ENH: refactored TUserManager and TAuthManager so that they are easier to be extended (Qiang) +ENH: template syntax now supports setting event handler via subproperties (Qiang) +CHG: Ticket#151 - URL format is modified to handle empty GET values (Qiang) +CHG: Ticket#153 - TAssetManager now ignores .svn directories (Qiang) +CHG: Changed TControl::onBubbleEvent() to TControl::bubbleEvent() (Qiang) +NEW: TTableHeaderRow, TTableFooterRow and table section support (Qiang) +NEW: TCompositeControl (Qiang) +NEW: TTextProcessor (Qiang) +NEW: TMarkdown (Wei) +NEW: Blog demo (Qiang) + +Version 3.0.0 May 1, 2006 +========================= +BUG: Ticket#68 - 2 TButtons with THtmlArea causes second button to fail to function (Wei) +BUG: Ticket#70 - Javascript for TDataTypeValidator added (Wei) +BUG: Ticket#131 - TImageMap and TLinkButton continue to postback even client validator fails (Wei) +BUG: Ticket#135 - TBrowserLogRoute reports wrong timings (Qiang) +BUG: Ticket#137 - The JavasciptLogger does not work (Qiang) +BUG: Ticket#138 - missing file in TDataGrid (Qiang) +BUG: Ticket#139 - TThemeManager::setBasePath() should understand an alias path (Qiang) +BUG: Non-control components can now use expressions in their properties (Qiang) +BUG: TControl.Visible did not make use of overriden getVisible() (Qiang) +BUG: TWizard did not stop navigation upon a validation failure (Qiang) +BUG: NumberFormat will now zero-fill numbers base on the pattern. (Wei) +ENH: TButton, TImageButton and TLinkButton now implement IButtonControl interface (Qiang) +ENH: TResponse::writeFile takes three additional parameters to allow sending memory data (Qiang) +ENH: TButtonColumn can now be a column of image buttons (Qiang) +ENH: TLiteral will display body content if Text is empty (Qiang) +ENH: Format string in classes extending TDataGridColumn can now evaluate an expression (Qiang) +ENH: Format string in classes extending TListControl can now evaluate an expression (Qiang) +ENH: Added THttpResponse::reload() (Qiang) +ENH: Custom visual effects can be added to client-side validators via ClientSide property in validators. (Wei) +ENH: TJavascript::encode() allows raw javascript code when string begins with "javascript:" (Wei) +ENH: Update TinyMCE to 2.0.5.1 +CHG: Rewrote client-side javascript validators, check your client-side validation behaviour (Wei) +CHG: Updated the javascript Prototype library, a few utilties functions REMOVED, may break your existing javascript code. (Wei) +CHG: Build javascript without compression, only comments are removed. (Wei) +CHG: TDatePicker's date can be set using Date property, it value must be in same format as DateFormat, TimeStamp must be set as integer (wei) +CHG: TSimpleDateFormatter::parse() now return an integer or null on parse error (Wei) +CHG: TControl::createControls() is changed to public. (Qiang) +CHG: Template comment tag is changed from to (Qiang) +NEW: TListControlValidator (Wei) +NEW: TClientScript (Wei) + + +Version 3.0RC2 April 16, 2006 +============================= +BUG: Ticket#54 - recursive reverse() definition (Wei) +BUG: Ticket#93 - ValidationGroup not working in TImageMap for js validator (Wei) +BUG: Ticket#97 - Invalid return type value for TSimpleDateFormatter::parse (Wei) +BUG: Ticket#118 - Variables that may not have been initialized (Qiang) +BUG: Ticket#121 - OnClick don't fire with TImageButton and TRequiredFieldValidator (Wei) +BUG: Ticket#129 - TRadioButtonList in TWizard step does not postback correctly (Qiang) +CHG: Moved localize() into PradoBase (Qiang) +CHG: List controls now use array keys as list item values even if the array is integer-indexed (Qiang) +CHG: THttpUtility::htmlEncode and htmlDecode now do not deal with & (Qiang) +CHG: Expressions appeared in a template are evaluated in PreRender stage (Qiang) +CHG: The context of expressions appeared in a template is changed to the template control (Qiang) +ENH: Optimized the representation and evaluation of template expressions (Qiang) +ENH: Added Raw layout to TDataList (Qiang) +ENH: Added TRepeater.DataKeys and TRepeater.DataKeyField (Qiang) + +Version 3.0RC1 April 5, 2006 +============================ +BUG: Ticket#85 - Undefined TDataGrid::setSelectedIndex (Qiang) +BUG: Ticket#87 - Typo in IDbConnection (Qiang) +BUG: Ticket#88 - Unclosed HTML tag in TDatePicker and TColorPicker (Qiang) +BUG: Ticket#89 - TInlineFrame::ScrollBars wrong behaviour expected (Qiang) +BUG: Ticket#95 - Typo in TTemplateControl::registerContentPlaceHolder (Qiang) +BUG: Ticket#103 - Typo in TStyle::setVerticalAlign (Qiang) +BUG: Ticket#107 - ListControls not respect parent disabling (Qiang) +BUG: SF#1432624 - Incorrect documentation about caching expiry (Qiang) +BUG: SF#1446846 - Typo in THead (Qiang) +BUG: THttpSession fails when user storage module is used (Qiang) +CHG: TTextHighlighter.EnableCopyCode defaults to false (Qiang) +CHG: Reorganized quickstart tutorial demo, added new sections (Qiang) +CHG: Reorganized folders under framework (Qiang) +CHG: Modified THtmlArea default toolbar and size. (Qiang) +CHG: Pagers in TDataGrid are now enclosed within panels (Qiang) +ENH: Ticket#92 - Support for user exception message file (Qiang) +ENH: Ticket#106 - Support for validation on THiddenField (Qiang) +ENH: Ticket#110 - Support for TVarDump with syntax highlight (Qiang) +ENH: TDataFieldAccessor can access public class variables (Qiang) +ENH: TPhpErrorException now shows the actual error lines (Qiang) +ENH: Refactored cache classes with support for cache dependency (Qiang) +NEW: TStack component (Qiang) +NEW: TImageMap control (Qiang) +NEW: TWizard control (Qiang) +NEW: TVarDumper and PradoBase::varDump() (Qiang) +NEW: TComponentReflection (Qiang) +NEW: TParameterModule (Qiang) +NEW: TPropelLogRoute (Jason) + +Version 3.0b March 6, 2006 +========================== +BUG: fixed many +CHG: event names must be prefixed with 'On' (Qiang) +CHG: values of properties whose name ends with 'Template' are parsed directly by template parser (Qiang) +ENH: template parser reports exact error location (Qiang) +ENH: cookie HMAC check (Qiang) +NEW: TInlineFrame (Jason) +NEW: TAPCCache (Alban) +NEW: TColorPicker, TDatePicker, TRatingList, TAdodbProvider, TCreoleProvider (Wei) +NEW: TMultiView, TView, TControlAdapter, TWebControlAdapter, TPagedList, TAttributeCollection (Qiang) + +Version 3.0a January 18, 2006 +============================= +Starting, main feaures ready (Qiang) diff --git a/buildscripts/texbuilder/Page2Tex.php b/buildscripts/texbuilder/Page2Tex.php new file mode 100644 index 00000000..c8cad6c9 --- /dev/null +++ b/buildscripts/texbuilder/Page2Tex.php @@ -0,0 +1,286 @@ +_base = $base; + $this->_current_page = $current; + $this->_dir = $dir; + } + + function setCurrentPage($current) + { + $this->_current_page = $current; + } + + function escape_verbatim($matches) + { + return "\begin{verbatim}".str_replace($this->_verb_find, $this->_verb_replace, $matches[1])."\end{verbatim}\n"; + } + + function escape_verb($matches) + { + $text = str_replace($this->_verb_find, $this->_verb_replace, $matches[1]); + return '\verb<'.$text.'<'; + } + + function include_image($matches) + { + + $current_path = $this->_current_page; + + $image = dirname($current_path).'/'.trim($matches[1]); + + $file = realpath($image); + $info = getimagesize($file); + switch($info[2]) + { + case 1: + $im = imagecreatefromgif($file); + break; + case 2: $im = imagecreatefromjpeg($file); break; + case 3: $im = imagecreatefrompng($file); break; + } + $base = $this->_base; + + if(isset($im)) + { + $prefix = strtolower(str_replace(realpath($base), '', $file)); + $filename = preg_replace('/\\\|\//', '_', substr($prefix,1)); + $filename = substr($filename, 0, strrpos($filename,'.')).'.png'; + $newfile = $this->_dir.'/'.$filename; + imagepng($im,$newfile); + imagedestroy($im); + + return $this->include_figure($info, $filename); + } + } + + function include_figure($info, $filename) + { + $width = sprintf('%0.2f', $info[0]/(135/2.54)); + return ' + \begin{figure}[!ht] + \centering + \includegraphics[width='.$width.'cm]{'.$filename.'} + \label{fig:'.$filename.'} + \end{figure} + '; + } + + function anchor($matches) + { + $page = $this->get_current_path(); + return '\hypertarget{'.$page.'/'.strtolower($matches[1]).'}{}'; + } + + function texttt($matches) + { + return '\texttt{'.str_replace(array('#','_'),array('\#','\_'), $matches[1]).'}'; + } + + function get_current_path() + { + $current_path = $this->_current_page; + $base = $this->_base; + $page = strtolower(substr(str_replace($base, '', $current_path),1)); + return $page; + } + + function make_link($matches) + { + if(is_int(strpos($matches[1], '#'))) + { + if(strpos($matches[1],'?') ===false) + { + $target = $this->get_current_path().'/'.substr($matches[1],1); + return '\hyperlink{'.$target.'}{'.$matches[2].'}'; + } + else + { + $page = strtolower(str_replace('?page=', '', $matches[1])); + $page = str_replace('.','/',$page); + $page = str_replace('#','.page/',$page); + return '\hyperlink{'.$page.'}{'.$matches[2].'}'; + } + } + else if(is_int(strpos($matches[1],'?'))) + { + $page = str_replace('?page=','',$matches[1]); + return '\hyperlink{'.$page.'}{'.$matches[2].'}'; + } + return '\href{'.$matches[1].'}{'.$matches[2].'}'; + } + + function parse_html($page,$html) + { + + + $html = preg_replace('/<\/?com:TContent[^>]*>/', '', $html); + $html = preg_replace('/<\/?p [^>]*>/', '', $html); + $html = preg_replace('/<\/?p>/', '', $html); + + $html = preg_replace('/(\s+|\(+|\[+)"/', '$1``', $html); + + //escape { and } + $html = preg_replace('/([^\s]+){([^}]*)}([^\s]+)/', '$1\\\{$2\\\}$3', $html); + + $html = preg_replace_callback('/"?[^>]*\/>/', array($this, 'include_image'), $html); + + //escape % + $html = str_replace('%', '\%', $html); + + //codes + $html = str_replace('$', '\$', $html); + + $html = preg_replace_callback('/]*>((.|\n)*?)<\/com:TTextHighlighter>/', array($this,'escape_verbatim'), $html); +// $html = preg_replace('/<\/com:TTextHighlighter>/', '`2`', $html); +// $html = preg_replace_callback('/(`1`)([^`]*)(`2`)/m', array($this,'escape_verbatim'), $html); + $html = preg_replace_callback('/(
)((.|\n)*?)(<\/div>)/', array($this,'escape_verbatim'), $html); + $html = preg_replace_callback('/(
)([^<]*)(<\/pre>)/', array($this,'escape_verbatim'), $html);
+	
+		//
+		$html = preg_replace_callback('/([^<]*)<\/code>/', array($this,'escape_verb'), $html);
+	
+		//runbar
+		$html = preg_replace('//',
+				'\href{http://www.pradosoft.com/demos/quickstart/index.php?page=$1}{$1 Demo}', $html);
+	
+		//DocLink
+		$html = preg_replace('//',
+	                        '\href{http://www.pradosoft.com/docs/manual/$1/$2.html}{$1.$2 API Reference}', $html);
+	
+		//text modifiers
+		$html = preg_replace('/]*>([^<]*)<\/b>/', '\textbf{$1}', $html);
+		$html = preg_replace('/]*>([^<]*)<\/i>/', '\emph{$1}', $html);
+		$html = preg_replace_callback('/([^<]*)<\/tt>/', array($this,'texttt'), $html);
+	
+		//links
+		$html = preg_replace_callback('/]+href="([^"]*)"[^>]*>([^<]*)<\/a>/',
+								array($this,'make_link'), $html);
+		//anchor
+		$html = preg_replace_callback('/]+name="([^"]*)"[^>]*><\/a>/', array($this,'anchor'), $html);
+	
+		//description 
+ $html = preg_replace('/
([^<]*)<\/dt>/', '\item[$1]', $html); + $html = preg_replace('/<\/?dd>/', '', $html); + $html = preg_replace('/
/', '\begin{description}', $html); + $html = preg_replace('/<\/dl>/', '\end{description}', $html); + + //item lists + $html = preg_replace('/]*>/', '\begin{itemize}', $html); + $html = preg_replace('/<\/ul>/', '\end{itemize}', $html); + $html = preg_replace('/]*>/', '\begin{enumerate}', $html); + $html = preg_replace('/<\/ol>/', '\end{enumerate}', $html); + $html = preg_replace('/]*>/', '\item ', $html); + $html = preg_replace('/<\/li>/', '', $html); + + //headings + $html = preg_replace('/([^<]+)<\/h1>/', '\section{$2}', $html); + $html = preg_replace('/([^<]+)<\/h2>/', '\subsection{$2}', $html); + $html = preg_replace('/([^<]+)<\/h3>/', '\subsubsection{$2}', $html); + + //div box + $html = preg_replace_callback('/
((.|\n)*?)<\/div>/', + array($this, 'mbox'), $html); + + //tabular + $html = preg_replace_callback('/\s*]*>((.|\n)*?)<\/table>/', + array($this, 'tabular'), $html); + + $html = html_entity_decode($html); + + return $html; + } + + function tabular($matches) + { + $options = array(); + foreach(explode(',', $matches[1]) as $string) + { + $sub = explode('=', trim($string)); + $options[trim($sub[0])] = trim($sub[1]); + } + + $widths = explode(' ',preg_replace('/\(|\)/', '', $options['width'])); + $this->_tabular_widths = $widths; + + $this->_tabular_total = count($widths); + $this->_tabular_col = 0; + + $begin = "\begin{table}[!hpt]\centering \n \begin{tabular}{".$options['align']."}\\hline"; + $end = "\end{tabular} \n \end{table}\n"; + $table = preg_replace('/<\/tr>/', '\\\\\\\\ \hline', $matches[2]); + $table = preg_replace('//', '', $table); + $table = preg_replace('/([^<]+)<\/th>/', '\textbf{$1} &', $table); + $table = preg_replace_callback('/((.|\n)*?)<\/td>/', array($this, 'table_column'), $table); + $table = preg_replace('/
/', ' \\\\\\\\', $table); + + $table = preg_replace('/&\s*\\\\\\\\/', '\\\\\\\\', $table); + return $begin.$table.$end; + } + + function table_column($matches) + { + $width = $this->_tabular_widths[$this->_tabular_col]; + if($this->_tabular_col >= $this->_tabular_total-1) + $this->_tabular_col = 0; + else + $this->_tabular_col++; + return '\begin{minipage}{'.$width.'\textwidth}\vspace{3mm}'. + $matches[1].'\vspace{3mm}\end{minipage} & '; + } + + function mbox($matches) + { + return "\n\begin{mybox}\n".$matches[1]."\n\end{mybox}\n"; + } + + function get_chapter_label($chapter) + { + return '\hypertarget{'.str_replace(' ', '', $chapter).'}{}'; + } + + function get_section_label($section) + { + $section = str_replace('.page', '', $section); + return '\hypertarget{'.str_replace('/', '.', $section).'}{}'; + } + + + function set_header_id($content, $count) + { + self::$header_count = $count*100; + $content = preg_replace_callback('/

/', array($this,"h1"), $content); + $content = preg_replace_callback('/

/', array($this,"h2"), $content); + $content = preg_replace_callback('/

/', array($this,"h3"), $content); + return $content; + } + + function h1($matches) + { + return "

"; + } + + function h2($matches) + { + return "

"; + } + + function h3($matches) + { + return "

"; + } + +} + +?> \ No newline at end of file diff --git a/demos/blog/protected/Pages/SearchPost.php b/demos/blog/protected/Pages/SearchPost.php index 53e8401c..a824d257 100644 --- a/demos/blog/protected/Pages/SearchPost.php +++ b/demos/blog/protected/Pages/SearchPost.php @@ -21,7 +21,7 @@ class SearchPost extends BlogPage foreach($keywords as $keyword) { if(($keyword=$this->DataAccess->escapeString(trim($keyword)))!=='') - $filter.=" AND content LIKE '%$keyword%'"; + $filter.=" AND (content LIKE '%$keyword%' OR title LIKE '%$keyword%')"; } return $filter; } diff --git a/demos/quickstart/protected/controls/TopicList.tpl b/demos/quickstart/protected/controls/TopicList.tpl index f26b75a6..1302338d 100644 --- a/demos/quickstart/protected/controls/TopicList.tpl +++ b/demos/quickstart/protected/controls/TopicList.tpl @@ -62,7 +62,7 @@

-
Avanced Topics
+
Advanced Topics
  • Collections
  • Authentication and Authorization
  • diff --git a/demos/quickstart/protected/pages/Advanced/Assets.page b/demos/quickstart/protected/pages/Advanced/Assets.page index e79f4ecf..8c7980a6 100644 --- a/demos/quickstart/protected/pages/Advanced/Assets.page +++ b/demos/quickstart/protected/pages/Advanced/Assets.page @@ -36,7 +36,7 @@ Asset publishing is managed by the System.Web.TAssetManager module. By

    Performance

    -PRADO uses caching techniques to ensure the efficiency of asset publishing. Publishing an asset essentially requires file copy operation, which is expensive. To save unnecessary file copy operations, System.Web.TAssetManager only publishes an asset when it has a newer file modification time than the published file. When an application runs under the Performance mode, such timestamp checkings are also omitted. +PRADO uses caching techniques to ensure the efficiency of asset publishing. Publishing an asset essentially requires file copy operation, which is expensive. To save unnecessary file copy operations, System.Web.TAssetManager only publishes an asset when it has a newer file modification time than the published file. When an application runs under the Performance mode, such timestamp checking is also omitted.

    ADVISORY: Do not overuse asset publishing. The asset concept is mainly used to help better reuse and redistribute component classes. Normally, you should not use asset publishing for resources that are not bound to any component in an application. For example, you should not use asset publishing for images that are mainly used as design elements (e.g. logos, background images, etc.) Let Web server to directly serve these images will help improve the performance of your application. diff --git a/demos/quickstart/protected/pages/Advanced/Collections.page b/demos/quickstart/protected/pages/Advanced/Collections.page index 7fc3443d..84883be4 100644 --- a/demos/quickstart/protected/pages/Advanced/Collections.page +++ b/demos/quickstart/protected/pages/Advanced/Collections.page @@ -2,7 +2,7 @@

    Collections

    -Collection is a basic data structure in programming. In traditional PHP programming, array is used widely to represent collection data structure. A PHP array is a mix of cardinal-indxed array and hash table. +Collection is a basic data structure in programming. In traditional PHP programming, array is used widely to represent collection data structure. A PHP array is a mix of cardinal-indexed array and hash table.

    To enable object-oriented manipulation of collections, PRADO provides a set of powerful collection classes. Among them, the TList and TMap are the most fundamental and usually serve as the base classes for other collection classes. Since many PRADO components have properties that are of collection type, it is very important for developers to master the usage of PRADO collection classes. diff --git a/demos/quickstart/protected/pages/Advanced/Error.page b/demos/quickstart/protected/pages/Advanced/Error.page index 9d5e3037..a0765c10 100644 --- a/demos/quickstart/protected/pages/Advanced/Error.page +++ b/demos/quickstart/protected/pages/Advanced/Error.page @@ -7,7 +7,7 @@ PRADO provides a complete error handling and reporting framework based on the PH

    Exception Classes

    -Errors occur in a PRADO application may be classified into three categories: those caused by PHP script parsing, those caused by wrong code (such as calling an undefined function, setting an unknown property), and those caused by improper use of the Web application by client users (such as attempting to access restricted pages). PRADO is unable to deal with the first category of errors because they cannot be caughted in PHP code. PRADO provides an exception hierarchy to deal with the second and third categories. +Errors occur in a PRADO application may be classified into three categories: those caused by PHP script parsing, those caused by wrong code (such as calling an undefined function, setting an unknown property), and those caused by improper use of the Web application by client users (such as attempting to access restricted pages). PRADO is unable to deal with the first category of errors because they cannot be caught in PHP code. PRADO provides an exception hierarchy to deal with the second and third categories.

    All errors in PRADO applications are represented as exceptions. The base class for all PRADO exceptions is TException. It provides the message internationalization functionality to all system exceptions. An error message may be translated into different languages according to the user browser's language preference. @@ -21,7 +21,7 @@ Exceptions raised due to improper usage of the PRADO framework inherit from

  • TInvalidDataTypeException - data type is incorrect or unexpected.
  • TInvalidDataFormatException - format of data is incorrect.
  • TInvalidOperationException - invalid operation request.
  • -
  • TPhpErrorException - caughtable PHP errors, warnings, notices, etc.
  • +
  • TPhpErrorException - catchable PHP errors, warnings, notices, etc.
  • TSecurityException - errors related with security.
  • TIOException - IO operation error, such as file open failure.
  • TDBException - errors related with database operations.
  • diff --git a/demos/quickstart/protected/pages/Advanced/I18N.page b/demos/quickstart/protected/pages/Advanced/I18N.page index 6c86a6c9..1330091f 100644 --- a/demos/quickstart/protected/pages/Advanced/I18N.page +++ b/demos/quickstart/protected/pages/Advanced/I18N.page @@ -18,7 +18,7 @@
  • Dates, Times.
  • Numbers, Currency, Measurements.
  • Phone numbers.
  • -
  • Honorifics and personal titles.
  • +
  • Honorific and personal titles.
  • Postal address.
  • Page layout.
@@ -26,7 +26,7 @@

If possible all manner of text should be isolated and store in a persistence format. These text include, application error messages, hard coded strings in PHP files, emails, static HTML text, and text on form elements (e.g. buttons).

Configuration

-

To enable the localization features in Prado, you need to add a few configuration options in your application configuration. +

To enable the localization features in PRADO, you need to add a few configuration options in your application configuration. First you need to include the System.I18N.* namespace to your paths.

@@ -98,7 +98,7 @@ Lastly, you can change the globalization settings on page by page basis using -

Localizing your Prado application

+

Localizing your PRADO application

There are two areas in your application that may need message or string localization, in PHP code and in the templates. To localize strings within PHP, use the localize function detailed below. To localize text in the template, use the
TTranslate component.

Using localize function to translate text within PHP

@@ -134,7 +134,7 @@ $message = Prado::localize("There are {num_users} users online.", array('num_use

Where the second parameter in localize takes an associative array with the key as the substitution to find in the text and replaced it with the associated value. The localize function does not solve the problem of localizing languages that have plural forms, the solution is to use TChoiceFormat.

-

The following sample demonstrates the basics of localization in Prado.

+

The following sample demonstrates the basics of localization in PRADO.

I18N Components

@@ -148,9 +148,9 @@ To translate a message or string in the template, use TTranslate.

<com:TTranslate Text="Goodbye" />
-

TTranslate can also perform string substitution. +

TTranslate can also perform string substitution. The Parameters property can be use to add name values pairs for substitution. Substrings in the translation enclosed with "{" and "}" are consider as the - parameter names during substitution lookup. The following example will substitute the substring "{time}" with the value of the parameter attribute "Parameters.time=<%= time() %>". + parameter names during substitution lookup. The following example will substitute the substring "{time}" with the value of the parameter attribute "Parameters.time=<%= time() %>". <com:TTranslate Parameters.time=<%= time() %> > The time is {time}. @@ -183,17 +183,17 @@ The time is {time}.

  • mediumtime
  • shorttime
  • -The predefined can be used in any combination. If using a combined predefined pattern, -the first pattern must be the date, followed by a space, and lastly the time pattern. -For example, full date pattern with short time pattern. The actual ordering of the -date-time and the actual pattern will be determine automatically from locale data specified +The predefined can be used in any combination. If using a combined predefined pattern, +the first pattern must be the date, followed by a space, and lastly the time pattern. +For example, full date pattern with short time pattern. The actual ordering of the +date-time and the actual pattern will be determine automatically from locale data specified by the Culture property.

    <com:TDateFormat Pattern="fulldate shorttime" /> -

    You can also specify a custom pattern using the following sub-patterns. +

    You can also specify a custom pattern using the following sub-patterns. The date/time format is specified by means of a string time pattern. In this pattern, all ASCII letters are reserved as pattern letters, which are defined as the following: Symbol Meaning Presentation Example @@ -222,22 +222,22 @@ The date/time format is specified by means of a string time pattern. In this pat

    The count of pattern letters determine the format.

    -

    (Text): 4 letters uses full form, less than 4, use short or abbreviated form +

    (Text): 4 letters uses full form, less than 4, use short or abbreviated form if it exists. (e.g., "EEEE" produces "Monday", "EEE" produces "Mon")

    (Number): the minimum number of digits. Shorter numbers are zero-padded - to this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is - handled specially; that is, if the count of 'y' is 2, the Year will be - truncated to 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) + to this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is + handled specially; that is, if the count of 'y' is 2, the Year will be + truncated to 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces "97".) Unlike other fields, fractional seconds are padded on the right with zero.

    -

    (Text and Number): 3 or over, use text, otherwise use number. (e.g., -"M" produces "1", "MM" produces "01", "MMM" produces "Jan", and "MMMM" +

    (Text and Number): 3 or over, use text, otherwise use number. (e.g., +"M" produces "1", "MM" produces "01", "MMM" produces "Jan", and "MMMM" produces "January".)

    -

    Any characters in the pattern that are not in the ranges of ['a'..'z'] -and ['A'..'Z'] will be treated as quoted text. For instance, characters -like ':', '.', ' ', and '@' will appear in the resulting time text +

    Any characters in the pattern that are not in the ranges of ['a'..'z'] +and ['A'..'Z'] will be treated as quoted text. For instance, characters +like ':', '.', ' ', and '@' will appear in the resulting time text even they are not embraced within single quotes.

    Examples using the US locale: @@ -259,7 +259,7 @@ Format Pattern Result

    TNumberFormat

    PRADO's Internationalization framework provide localized currency formatting and number formatting. Please note that the TNumberFormat component provides formatting only, it does not perform current conversion or exchange.

    -

    Numbers can be formatted as currency, percentage, decimal or scientific +

    Numbers can be formatted as currency, percentage, decimal or scientific numbers by specifying the Type attribute. The valid types are:

    • currency
    • @@ -268,7 +268,7 @@ numbers by specifying the Type attribute. The valid types are:
    • scientific

    - + <com:TNumberFormat Type="currency" Value="100" /> @@ -283,13 +283,13 @@ then also from the de_DE locale. This may lead to some confusion becaus people from US uses the "," (comma) as thousand separator. Therefore a Currency attribute is available, so that the output from the following example results in $100.00 -<com:TNumberFormat Type="currency" +<com:TNumberFormat Type="currency" Culture="en_US" Currency="EUR" Value="100" />

    -

    The Pattern property determines the number of digits, thousand grouping -positions, the number of decimal points and the decimal position. The actual characters that +

    The Pattern property determines the number of digits, thousand grouping +positions, the number of decimal points and the decimal position. The actual characters that are used to represent the decimal points and thousand points are culture specific and will change automatically according to the Culture property. The valid Pattern characters are: @@ -299,7 +299,7 @@ and will change automatically according to the Culture property. The va

  • . (full stop) - the position of the decimal point (only 1 decimal point is allowed)
  • , (comma) - thousand point separation (up to 2 commas are allowed)
  • -For example, consider the Value="1234567.12345" and +For example, consider the Value="1234567.12345" and with Culture="en_US" (which uses "," for thousand point separator and "." for decimal separators). Pattern Output diff --git a/demos/quickstart/protected/pages/Advanced/Logging.page b/demos/quickstart/protected/pages/Advanced/Logging.page index 5270a00d..9c7ec15e 100644 --- a/demos/quickstart/protected/pages/Advanced/Logging.page +++ b/demos/quickstart/protected/pages/Advanced/Logging.page @@ -52,10 +52,10 @@ Messages can be filtered according to their log levels and categories. Each log Log levels defined in System.Util.TLogger include : DEBUG, INFO, NOTICE, WARNING, ERROR, ALERT, FATAL. Messages can be filtered according log level criteria. For example, if a filter specifies WARNING and ERROR levels, then only those messages that are of WARNING and ERROR will be returned.

    -Message categories are hierarchical. A category whose name is the prefix of another is said to be the ancestor category of the other category. For example, System.Web category is the ancestor of System.Web.UI and System.Web.UI.WebControls categories. Messages can be selectively retrieved using such hierarchical category filters. For example, if the category filter is System.Web, then all messages in the System.Web are returned. In addition, messages in the childd categories, such as System.Web.UI.WebControls, are also returned. +Message categories are hierarchical. A category whose name is the prefix of another is said to be the ancestor category of the other category. For example, System.Web category is the ancestor of System.Web.UI and System.Web.UI.WebControls categories. Messages can be selectively retrieved using such hierarchical category filters. For example, if the category filter is System.Web, then all messages in the System.Web are returned. In addition, messages in the child categories, such as System.Web.UI.WebControls, are also returned.

    -By convention, the messages logged in the core code of PRADO are categorized according to the namespace of the corresponding classes. For example, messsages logged in TPage will be of category System.Web.UI.TPage. +By convention, the messages logged in the core code of PRADO are categorized according to the namespace of the corresponding classes. For example, messages logged in TPage will be of category System.Web.UI.TPage.

    \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Advanced/Scripts.page b/demos/quickstart/protected/pages/Advanced/Scripts.page index 5921e865..8fa1e27e 100644 --- a/demos/quickstart/protected/pages/Advanced/Scripts.page +++ b/demos/quickstart/protected/pages/Advanced/Scripts.page @@ -5,46 +5,46 @@ Quick guide to somewhat advanced JavaScript tour of some OO features by Serg

    Hey, I didn't know you could do that

    - If you are a web developer and come from the same place I do, you have probably - used quite a bit of Javascript in your web pages, mostly as UI glue. + If you are a web developer and come from the same place I do, you have probably + used quite a bit of Javascript in your web pages, mostly as UI glue.

    Until recently, I knew that Javascript had more OO capabilities than I was employing, - but I did not feel like I needed to use it. As the browsers started to support a more - standardized featureset of Javascript and the DOM, it became viable to write more - complex and functional code to run on the client. That helped giving birth to the + but I did not feel like I needed to use it. As the browsers started to support a more + standardized featureset of Javascript and the DOM, it became viable to write more + complex and functional code to run on the client. That helped giving birth to the AJAX phenomena.

    - As we all start to learn what it takes to write our cool, AJAXy applications, we begin - to notice that the Javascript we used to know was really just the tip of the iceberg. - We now see Javascript being used beyond simple UI chores like input validation and frivolous - tasks. The client code now is far more advanced and layered, much like a real desktop - application or a client-server thick client. We see class libraries, object models, - hierarchies, patterns, and many other things we got used to seeing only in our server + As we all start to learn what it takes to write our cool, AJAX applications, we begin + to notice that the Javascript we used to know was really just the tip of the iceberg. + We now see Javascript being used beyond simple UI chores like input validation and frivolous + tasks. The client code now is far more advanced and layered, much like a real desktop + application or a client-server thick client. We see class libraries, object models, + hierarchies, patterns, and many other things we got used to seeing only in our server side code.

    - In many ways we can say that suddenly the bar was put much higher than before. It takes - a heck lot more proficiency to write applications for the new Web and we need to improve + In many ways we can say that suddenly the bar was put much higher than before. It takes + a heck lot more proficiency to write applications for the new Web and we need to improve our Javascript skills to get there. - If you try to use many of the existing javascript libraries out there, like - Prototype.js, - Scriptaculous, - moo.fx, - Behaviour, - YUI, - etc you'll eventually find yourself reading the JS code. Maybe because you want - to learn how they do it, or because you're curious, or more often because that's the - only way to figure out how to use it, since documentation does not seem to be highly - regarded with most of these libraries. Whatever the case may be, you'll face some - kung-fu techniques that will be foreign and scary if you haven't seen anything like + If you try to use many of the existing javascript libraries out there, like + Prototype.js, + Scriptaculous, + moo.fx, + Behaviour, + YUI, + etc you'll eventually find yourself reading the JS code. Maybe because you want + to learn how they do it, or because you're curious, or more often because that's the + only way to figure out how to use it, since documentation does not seem to be highly + regarded with most of these libraries. Whatever the case may be, you'll face some + kung-fu techniques that will be foreign and scary if you haven't seen anything like that before.

    - The purpose of this article is precisely explaining the types of constructs that + The purpose of this article is precisely explaining the types of constructs that many of us are not familiar with yet.

    @@ -52,12 +52,12 @@ Quick guide to somewhat advanced JavaScript tour of some OO features by Serg

    JSON (JavaScript Object Notation)

    JavaScript Object Notation (JSON,) is one of the new - buzzwords popping up around the AJAX theme. JSON, simply put, is a way of - declaring an object in javascript. Let's see an example right away and note + buzzwords popping up around the AJAX theme. JSON, simply put, is a way of + declaring an object in Javascript. Let's see an example right away and note how simple it is.

    -var myPet = { color: 'black', leg_count: 4, communicate: function(repeatCount){ +var myPet = { color: 'black', leg_count: 4, communicate: function(repeatCount){ for(i=0;i<repeatCount;i++) alert('Woof!');} }; @@ -65,10 +65,10 @@ for(i=0;i<repeatCount;i++) alert('Woof!');} }; Let's just add little bit of formatting so it looks more like how we usually find out there:

    -var myPet = +var myPet = { - color: 'black', - legCount: 4, + color: 'black', + legCount: 4, communicate: function(repeatCount) { for(i=0;i<repeatCount;i++) @@ -77,14 +77,14 @@ var myPet = };

    - Here we created a reference to an object with two properties (color - and legCount) and a method (communicate.) + Here we created a reference to an object with two properties (color + and legCount) and a method (communicate.) It's not hard to figure out that the object's properties and methods - are defined as a comma delimited list. Each of the members is introduced by name, followed - by a colon and then the definition. In the case of the properties it is easy, just the value - of the property. The methods are created by assigning an anonymous function, which we will + are defined as a comma delimited list. Each of the members is introduced by name, followed + by a colon and then the definition. In the case of the properties it is easy, just the value + of the property. The methods are created by assigning an anonymous function, which we will explain better down the line. - After the object is created and assigned to the variable myPet, + After the object is created and assigned to the variable myPet, we can use it like this:

    @@ -95,13 +95,13 @@ alert('my pet has ' + myPet.legCount + ' legs'); myPet.communicate(3);

    - You'll see JSON used pretty much everywhere in JS these days, as arguments to functions, + You'll see JSON used pretty much everywhere in JS these days, as arguments to functions, as return values, as server responses (in strings,) etc.

    What do you mean? A function is an object too?

    - This might be unusual to developers that never thought about that, but in JS a function is + This might be unusual to developers that never thought about that, but in JS a function is also an object. You can pass a function around as an argument to another function just like you can pass a string, for example. This is extensively used and very handy.

    @@ -110,7 +110,7 @@ myPet.communicate(3); Take a look at this example. We will pass functions to another function that will use them.

    -var myDog = +var myDog = { bark: function() { @@ -118,7 +118,7 @@ var myDog = } }; -var myCat = +var myCat = { meow: function() { @@ -138,9 +138,9 @@ annoyThePet(myDog.bark); annoyThePet(myCat.meow);

    - Note that we pass myDog.bark and myCat.meow without appending parenthesis - "()" to them. If we did that we would not be passing - the function, rather we would be calling the method and passing the return value, + Note that we pass myDog.bark and myCat.meow without appending parenthesis + "()" to them. If we did that we would not be passing + the function, rather we would be calling the method and passing the return value, undefined in both cases here.

    @@ -162,7 +162,7 @@ var a = new Array(); var b = [];

    - As I'm sure you already know, you can access individual items in an array + As I'm sure you already know, you can access individual items in an array by using the square brackets:

    @@ -173,8 +173,8 @@ var v3 = a[2];

    - But you are not limited to numeric indices. You can access any member of a JS - object by using its name, in a string. The following example creates an empty + But you are not limited to numeric indices. You can access any member of a JS + object by using its name, in a string. The following example creates an empty object, and adds some members by name.

    @@ -187,7 +187,7 @@ obj['some_function'] = function(){ /* do something */}; The above code has identical effect as the following:

    -var obj = +var obj = { member_1:'this is the member value', flag_2: false, @@ -196,7 +196,7 @@ var obj =

    - In many ways, the idea of objects and associative arrays (hashes) in JS are not + In many ways, the idea of objects and associative arrays (hashes) in JS are not distiguishable. The following two lines do the same thing too.

    @@ -208,8 +208,8 @@ obj['some_function']();

    Enough about objects, may I have a class now?

    - The great power of object oriented programming languages derive from the use - of classes. I don't think I would have guessed how classes are defined in JS + The great power of object oriented programming languages derive from the use + of classes. I don't think I would have guessed how classes are defined in JS using only my previous experience with other languages. Judge for yourself.

    @@ -225,27 +225,27 @@ var famousDog = new Pet('Santa\'s Little Helper', 15); alert('This pet is called ' + famousDog.name);

    - Let's see how we add a method to our Pet class. We will be using the - prototype property that all classes have. The prototype - property is an object that contains all the members that any object of the class will have. - Even the default JS classes, like String, Number, - and Date have a prototype object that we + Let's see how we add a method to our Pet class. We will be using the + prototype property that all classes have. The prototype + property is an object that contains all the members that any object of the class will have. + Even the default JS classes, like String, Number, + and Date have a prototype object that we can add methods and properties to and make any object of that class automatically gain this new member.

    Pet.prototype.communicate = function() -{ +{ alert('I do not know what I should say, but my name is ' + this.name); };

    - That's when a library like prototype.js comes in + That's when a library like prototype.js comes in handy. If we are using prototype.js, we can make our code look cleaner (at least in my opinion.)

    var Pet = Class.create(); -Pet.prototype = +Pet.prototype = { //our 'constructor' initialize: function(petName, age) @@ -258,7 +258,7 @@ Pet.prototype = { alert('I do not know what I should say, but my name is ' + this.name); } -}; +};

    Functions as arguments, an interesting pattern

    @@ -275,15 +275,15 @@ myArray.each( function(item, index)

    - Whoa! Let's explain what is going on here before you decide I've gone too + Whoa! Let's explain what is going on here before you decide I've gone too far and navigate to a better article than this one.

    - First of all, in the above example we are using the prototype.js library, which - adds the each function to the Array class. The each function accepts one - argument that is a function object. This function, in turn, will be called once - for each item in the array, passing two arguments when called, the item and the index - for the current item. Let's call this function our iterator function. + First of all, in the above example we are using the prototype.js library, which + adds the each function to the Array class. The each function accepts one + argument that is a function object. This function, in turn, will be called once + for each item in the array, passing two arguments when called, the item and the index + for the current item. Let's call this function our iterator function. We could have also written the code like this.

    @@ -296,24 +296,24 @@ var myArray = ['first', 'second', 'third']; myArray.each( myIterator );

    - But then we would not be doing like all the cool kids in school, right? - More seriously, though, this last format is simpler to understand but causes - us to jump around in the code looking for the myIterator function. It's nice - to have the logic of the iterator function right there in the same place - it's called. Also, in this case, we will not need the iterator function anywhere - else in our code, so we can transform it into an anonymous function without penalty. + But then we would not be doing like all the cool kids in school, right? + More seriously, though, this last format is simpler to understand but causes + us to jump around in the code looking for the myIterator function. It's nice + to have the logic of the iterator function right there in the same place + it's called. Also, in this case, we will not need the iterator function anywhere + else in our code, so we can transform it into an anonymous function without penalty.

    This is this but sometimes this is also that

    - One of the most common troubles we have with JS when we start writing our code - it the use of the this keyword. It could be a real + One of the most common troubles we have with JS when we start writing our code + it the use of the this keyword. It could be a real tripwire.

    - As we mentioned before, a function is also an object in JS, and sometimes we - do not notice that we are passing a function around. + As we mentioned before, a function is also an object in JS, and sometimes we + do not notice that we are passing a function around.

    Take this code snippet as an example. @@ -331,15 +331,15 @@ myButton2.onclick = buttonClicked;

    Because the buttonClicked function is defined outside any object we may tend to - think the this keyword will contain a reference to - the window or document + think the this keyword will contain a reference to + the window or document object (assuming this code is in the middle of an HTML page viewed in a browser.)

    But when we run this code we see that it works as intended and displays the id of - the clicked button. What happened here is that we made the onclick method of each button contain the - buttonClicked object reference, replacing whatever was there before. Now + the clicked button. What happened here is that we made the onclick method of each button contain the + buttonClicked object reference, replacing whatever was there before. Now whenever the button is clicked, the browser will execute something similar to the following line.

    @@ -347,11 +347,11 @@ myButton.onclick();

    - That isn't so confusing afterall, is it? But see what happens you start having other + That isn't so confusing afterall, is it? But see what happens you start having other objects to deal with and you want to act on these object upon events like the button's click.

    -var myHelper = +var myHelper = { formFields: [ ], emptyAllFields: function() @@ -377,18 +377,18 @@ var clearButton = document.getElementById('btnClear'); clearButton.onclick = myHelper.emptyAllFields;

    - So you think, nice, now I can click the Clear button on my page and those three text boxes - will be emptied. Then you try clicking the button only to get a runtime error. The error - will be related to (guess what?) the this keyword. - The problem is that this.formFields is not defined if - this containz a referece to the button, which is + So you think, nice, now I can click the Clear button on my page and those three text boxes + will be emptied. Then you try clicking the button only to get a runtime error. The error + will be related to (guess what?) the this keyword. + The problem is that this.formFields is not defined if + this contains a referece to the button, which is precisely what's happening. One quick solution would be to rewrite our last line of code.

    clearButton.onclick = function() -{ - myHelper.emptyAllFields(); +{ + myHelper.emptyAllFields(); };

    diff --git a/demos/quickstart/protected/pages/Advanced/Scripts1.page b/demos/quickstart/protected/pages/Advanced/Scripts1.page index f11a2f9d..ef0b6317 100644 --- a/demos/quickstart/protected/pages/Advanced/Scripts1.page +++ b/demos/quickstart/protected/pages/Advanced/Scripts1.page @@ -6,8 +6,8 @@ Developer Notes for prototype.js by Sergio Pereira.

    What is that?

    -In case you haven't already used it, prototype.js is a - JavaScript library written by Sam Stephenson. +In case you haven't already used it, prototype.js is a + JavaScript library written by Sam Stephenson. This amazingly well thought and well written piece of standards-compliant code takes a lot of the burden associated with creating rich, highly interactive web pages that characterize the Web 2.0 off your back.

    @@ -20,7 +20,7 @@ In case you haven't already used it, protot

    As you read the examples and the reference, developers familiar with the Ruby - programming language will notice an intentional similarity between Ruby's + programming language will notice an intentional similarity between Ruby's built-in classes and many of the extensions implemented by this library.

    @@ -32,7 +32,7 @@ In case you haven't already used it,
    protot

    - Unlike the DOM function, though, this one goes further. You can pass more than one id and + Unlike the DOM function, though, this one goes further. You can pass more than one id and $() will return an Array object with all the requested elements. The example below should illustrate this.

    @@ -76,7 +76,7 @@ function test2()

    Using the $F() function

    - The $F() function is a another welcome shortcut. It returns the value of any field input control, + The $F() function is a another welcome shortcut. It returns the value of any field input control, like text boxes or drop-down lists. The function can take as argument either the element id or the element object itself.

    @@ -100,9 +100,9 @@ function test3() into an Array object.

    - This function, combined with the extensions for the Array class, - makes it easier to convert or copy any enumerable list into an - Array object. One suggested use is to convert DOM + This function, combined with the extensions for the Array class, + makes it easier to convert or copy any enumerable list into an + Array object. One suggested use is to convert DOM NodeLists into regular arrays, which can be traversed more efficiently. See example below.

    @@ -113,8 +113,8 @@ function test3() - - + +

    - This <a href="http://othersite.com/page.html">text</a> has - a <a href="#localAnchor">lot</a> of - <a href="#otherAnchor">links</a>. Some are + This <a href="http://othersite.com/page.html">text</a> has + a <a href="#localAnchor">lot</a> of + <a href="#otherAnchor">links</a>. Some are <a href="http://wherever.com/page.html">external</a> and some are <a href="#someAnchor">local</a>

    -

    @@ -309,21 +309,21 @@ function showLocalLinks(paragraph)

    Enumerable Functions

    The sample data for the following examples. -var Fixtures = +var Fixtures = { - Products: + Products: [ {name: 'Basecamp', company: '37signals', type: 'Project Management'}, {name: 'Shopify', company: 'JadedPixel', type: 'E-Commerce'}, {name: 'Mint', company: 'Shaun Inman',type: 'Statistics'} ], - Artist: + Artist: [ - 'As I Lay Dying', - '36 Crazyfist', - 'Shadows Fall', - 'Trivium', + 'As I Lay Dying', + '36 Crazyfist', + 'Shadows Fall', + 'Trivium', 'In Flames' ], @@ -334,11 +334,11 @@ var F = Fixtures;

    Enumerable.each function

    -

    I used to find myself writing a lot of for loops. Although, -Prototype doesn’t by any means eliminate the need to do for loops, +

    I used to find myself writing a lot of for loops. Although, +Prototype does not by any means eliminate the need to do for-loops, it does give you access to what I consider to be a cleaner, easier to read method in each. -for(var i = 0; i < F.Numbers.length; i++) +for(var i = 0; i < F.Numbers.length; i++) { Logger.info(F.Numbers[i]); } @@ -347,7 +347,7 @@ for(var i = 0; i < F.Numbers.length; i++) The each function allows us to iterate over these objects Ruby style.

    -F.Numbers.each(function(num) +F.Numbers.each(function(num) { Logger.info(num); }); @@ -363,14 +363,14 @@ F.Numbers.each(function(num) 9 -

    The each function takes one argument, an iterator function. -This iterator is invoked once for every item in the array, and that item -along with the optional index is passed to the iterator. So if +

    The each function takes one argument, an iterator function. +This iterator is invoked once for every item in the array, and that item +along with the optional index is passed to the iterator. So if we also needed the index we could do something like the code below.

    -F.Numbers.each(function(num, index) +F.Numbers.each(function(num, index) { Logger.info(index + ": " + num); }); @@ -387,11 +387,11 @@ F.Numbers.each(function(num, index)

    Hash key/value pairs

    -

    Hashes can be created by wrapping an Object (associative array) in +

    Hashes can be created by wrapping an Object (associative array) in $H() and can have their key/value pairs exposed.

    -$H(F.Products[0]).each(function(product) +$H(F.Products[0]).each(function(product) { Logger.info(product.key + ": " + product.value); }); @@ -406,19 +406,19 @@ We can also directly access the keys and values of a Hash without iterating over

    $H(F.Products[1]).keys(); -//Outputs name,company,type +//Outputs name,company,type $H(F.Products[1]).values(); -//Outputs Shopify,JadedPixel,E-Commerce +//Outputs Shopify,JadedPixel,E-Commerce

    Enumerable.collect function

    -

    The collect function allows you to iterate over an Array and return the -results as a new array. Each item returned as a result of the iteration will be +

    The collect function allows you to iterate over an Array and return the +results as a new array. Each item returned as a result of the iteration will be pushed onto the end of the new array.

    -var companies = F.Products.collect(function(product) +var companies = F.Products.collect(function(product) { return product.company; }); @@ -431,7 +431,7 @@ Logger.info(companies.join(', '));

    You can even join on the end of the block.

    -return F.Products.collect(function(product) +return F.Products.collect(function(product) { return product.company; }).join(', '); @@ -439,9 +439,9 @@ return F.Products.collect(function(product)

    Enumerable.include function

    -

    The include function allows you to check if a value is included in an array -and returns true or false depending on if a match was made. Assuming I put -up a form asking the user to name some artist in my iTunes playlist, +

    The include function allows you to check if a value is included in an array +and returns true or false depending on if a match was made. Assuming I put +up a form asking the user to name some artist in my iTunes playlist, we could do something like the code below. Prime candidate for some conditional madness.

    @@ -450,11 +450,11 @@ return F.Artists.include('Britney Spears'); // returns false

    Enumerable.inject function

    -

    The inject function is good for getting a collective sum from an array of +

    The inject function is good for getting a collective sum from an array of values. For instance, to add up all the numbers.

    -var score = F.Numbers.inject(0, function(sum, value) +var score = F.Numbers.inject(0, function(sum, value) { return sum + value; }); @@ -463,19 +463,19 @@ Logger.info(score); //Output 161 -

    The first argument to inject is just an initial value that +

    The first argument to inject is just an initial value that would be added to the sum, so if we added 1 instead of 0, the output would be 162.

    Enumerable.findAll function

    -When given an Array, the findAll function will return an array of -items for which the iterator evaluated to true. Basically, it allows you to -build a new array of values based on some search criteria. -If we wanted to find all products whose type was “E-Commerce” +When given an Array, the findAll function will return an array of +items for which the iterator evaluated to true. Basically, it allows you to +build a new array of values based on some search criteria. +If we wanted to find all products whose type was “E-Commerce” we could do something like the code below.

    -var ecom = F.Products.findAll(function(product) +var ecom = F.Products.findAll(function(product) { return product.type == 'E-Commerce'; }); @@ -486,18 +486,18 @@ Logger.info(ecom[0].company + " produces " + ecom[0].name); JadedPixel produces Shopify -

    Note that even if only one match is made, just as in this case, -the result is still returned as an array. In that case, +

    Note that even if only one match is made, just as in this case, +the result is still returned as an array. In that case, ecom.company would return undefined.

    Enumerable.detect function

    -

    Unlike the findAll function, the detect function will only -return the first item for which the expression inside -the iterator is true. So, if we wanted to find the first number that +

    Unlike the findAll function, the detect function will only +return the first item for which the expression inside +the iterator is true. So, if we wanted to find the first number that was greater than 5 we’d do something like the code below.

    -var low = F.Numbers.detect(function(num) +var low = F.Numbers.detect(function(num) { return num > 5 }); @@ -506,13 +506,13 @@ Logger.info(low); //Outputs 98 -

    Even though, there are other numbers above 5 in our array, detect +

    Even though, there are other numbers above 5 in our array, detect only gives us the first match back.

    Enumerable.invoke function

    -

    The invoke function allows us to pass a method as a string and -have that method invoked. For instance, if we wanted to sort +

    The invoke function allows us to pass a method as a string and +have that method invoked. For instance, if we wanted to sort our array of artists we’d do something like this:

    @@ -520,7 +520,7 @@ our array of artists we’d do something like this:

    //Outputs 36 Crazyfist,As I Lay Dying,In Flames,Shadows Fall,Trivium
    -

    Why not just use F.Artists.sort? Well, for the example above +

    Why not just use F.Artists.sort? Well, for the example above we could do just that, but here is where invoke shines.

    @@ -533,8 +533,8 @@ we could do just that, but here is where invoke shines.

    F.Artists.invoke('sort');
    -

    The reason this will not work is because it is taking each item -in that array and trying to apply sort to it, thus if we wrote it outright, +

    The reason this will not work is because it is taking each item +in that array and trying to apply sort to it, thus if we wrote it outright, it would look something like this:

    @@ -548,8 +548,8 @@ F.Artists.invoke('toLowerCase');

    -Now, what about passing arguments to the invoke function? -The first argument passed to invoke is the method to be invoked, +Now, what about passing arguments to the invoke function? +The first argument passed to invoke is the method to be invoked, and any other arguments beyond that will be passed as arguments to the invoked method.

    diff --git a/demos/quickstart/protected/pages/Advanced/Scripts2.page b/demos/quickstart/protected/pages/Advanced/Scripts2.page index 6ee6a5d8..2c9ce220 100644 --- a/demos/quickstart/protected/pages/Advanced/Scripts2.page +++ b/demos/quickstart/protected/pages/Advanced/Scripts2.page @@ -9,13 +9,13 @@ Event.observe(element, name, observer, [useCapture]); -

    Assuming for a moment that we want to observe when a link was clicked, +

    Assuming for a moment that we want to observe when a link was clicked, we could do the following:

    // <a id="clicker" href="http://foo.com">Click me</a> Event.observe('clicker', 'click', function(event) -{ +{ alert('clicked!'); }); @@ -24,7 +24,7 @@ Event.observe('clicker', 'click', function(event) Event.observe('clicker', 'click', function(event) -{ +{ alert(Event.element(event)); }); @@ -35,8 +35,8 @@ Event.observe('clicker', 'click', function(event) Event.observe(document, 'keypress', function(event) -{ - if(Event.keyCode(event) == Event.KEY_TAB) +{ + if(Event.keyCode(event) == Event.KEY_TAB) alert('Tab Pressed'); }); @@ -44,37 +44,37 @@ Event.observe(document, 'keypress', function(event)

    And lets say we wanted to keep track of what has been typed :

    -Event.observe('search', 'keypress', function(event) -{ +Event.observe('search', 'keypress', function(event) +{ Element.update('search-results', $F(Event.element(event))); }); -

    Prototype defines properties inside the event object for some -of the more common keys, so feel free to dig around in Prototype to +

    Prototype defines properties inside the event object for some +of the more common keys, so feel free to dig around in Prototype to see which ones those are.

    -

    A final note on keypress events; If you'd like to detect a +

    A final note on keypress events; If you'd like to detect a left click you can use Event.isLeftClick(event).

    Getting the coordinates of the mouse pointer

    -

    Drag and drop, dynamic element resizing, games, and -much more all require the ability to track the X and Y location of -the mouse. Prototype makes this fairly simple. The code below tracks -the X and Y position of the mouse and spits out those values into +

    Drag and drop, dynamic element resizing, games, and +much more all require the ability to track the X and Y location of +the mouse. Prototype makes this fairly simple. The code below tracks +the X and Y position of the mouse and spits out those values into an input box named mouse.

    Event.observe(document, 'mousemove', function(event) { - $('mouse').value = "X: " + Event.pointerX(event) + + $('mouse').value = "X: " + Event.pointerX(event) + "px Y: " + Event.pointerY(event) + "px"; }); -

    If we wanted to observe the mouse location when it was -hovering over a certain element, we'd just change the document argument to +

    If we wanted to observe the mouse location when it was +hovering over a certain element, we'd just change the document argument to the id or element that was relevant.

    Stopping Propagation

    @@ -83,33 +83,33 @@ the id or element that was relevant.

    Events, Binding, and Objects

    -

    Everything has been fairly straight forward so far, but things -start getting a little tricker when you need to work with events in -and object-oriented environment. You have to deal with binding and funky +

    Everything has been fairly straight forward so far, but things +start getting a little trickier when you need to work with events in +and object-oriented environment. You have to deal with binding and funky looking syntax that might take a moment to get your head around.

    Lets look at some code so you can get a better understanding of what I'm talking about.

    EventDispenser = Class.create(); -EventDispenser.prototype = +EventDispenser.prototype = { - initialize: function(list) + initialize: function(list) { this.list = list; - // Observe clicks on our list items - $$(this.list + " li").each(function(item) + // Observe clicks on our list items + $$(this.list + " li").each(function(item) { Event.observe(item, 'click', this.showTagName.bindEvent(this)); }.bind(this)); - // Observe when a key on the keyboard is pressed. - // In the observer, we check for + // Observe when a key on the keyboard is pressed. + // In the observer, we check for // the tab key and alert a message if it is pressed. Event.observe(document, 'keypress', this.onKeyPress.bindEvent(this)); - // Observe our fake live search box. When a user types - // something into the box, the observer will take that + // Observe our fake live search box. When a user types + // something into the box, the observer will take that // value(-1) and update our search-results div with it. Event.observe('search', 'keypress', this.onSearch.bindEvent(this)); @@ -117,65 +117,65 @@ EventDispenser.prototype = }, // Arbitrary functions to respond to events - showTagName: function(event) + showTagName: function(event) { alert(Event.element(event).tagName); }, - onKeyPress: function(event) + onKeyPress: function(event) { var code = event.keyCode; - if(code == Event.KEY_TAB) + if(code == Event.KEY_TAB) alert('Tab key was pressed'); }, - onSearch: function(event) + onSearch: function(event) { Element.update('search-results', $F(Event.element(event))); }, - onMouseMove: function(event) + onMouseMove: function(event) { - $('mouse').value = "X: " + Event.pointerX(event) + + $('mouse').value = "X: " + Event.pointerX(event) + "px Y: " + Event.pointerY(event) + "px"; } } -

    Whoa! What's going on here? Well, we've defined our a -custom class EventDispenser. We're going to be using this class -to setup events for our document. Most of this code is a -rewrite of the code we looked at earlier except this time, we +

    Whoa! What's going on here? Well, we've defined our a +custom class EventDispenser. We're going to be using this class +to setup events for our document. Most of this code is a +rewrite of the code we looked at earlier except this time, we are working from inside an object.

    -

    Looking at the initialize method, we can really see how +

    Looking at the initialize method, we can really see how things are different now. Take a look at the code below:

    -// Observe clicks on our list items -$$(this.list + " li").each(function(item) +// Observe clicks on our list items +$$(this.list + " li").each(function(item) { Event.observe(item, 'click', this.showTagName.bindEvent(this)); }.bind(this)); -

    We've got iterators, binding and all sorts of stuff going on. +

    We've got iterators, binding and all sorts of stuff going on. Lets break down what this chunk of code is doing.

    -

    First we are hunting for a collection of elements based on -it's Css selector. This uses the Prototype selector function $$(). -After we've found the list items we are dealing with we send +

    First we are hunting for a collection of elements based on +it's CSS selector. This uses the Prototype selector function $$(). +After we've found the list items we are dealing with we send those into an each iteration where we will add our observers.

    Event.observe(item, 'click', this.showTagName.bindEvent(this)); -

    Now looking at the code above, you'll notice the bindEvent function. -This takes the method before it showTagName and treats it as the -method that will be triggered when, in this case, +

    Now looking at the code above, you'll notice the bindEvent function. +This takes the method before it showTagName and treats it as the +method that will be triggered when, in this case, someone clicks one of our list items.

    -

    You'll also notice we pass this as an argument to the bindEvent function. -This simply allows us to reference the object in context EventDispenser +

    You'll also notice we pass this as an argument to the bindEvent function. +This simply allows us to reference the object in context EventDispenser inside our function showTagName(event). If the showTagName function requires additional parameters, you can attach them to the later parameters of bindEvent. For example

    @@ -185,58 +185,58 @@ this.showTagName.bindEvent(this, param1, param2); showTime : function (event, param1, param2) { ... } -

    Moving on, you'll see bind(this) attached to our iterator function. -This really has nothing to do with events, it is only here to allow me to -use this inside the iterator. If we didn't use bind(this), I couldn't +

    Moving on, you'll see bind(this) attached to our iterator function. +This really has nothing to do with events, it is only here to allow me to +use this inside the iterator. If we did not use bind(this), I could not reference the method showTagName inside the iterator.

    -

    Ok, so we'll move on to looking at our methods that actually get +

    Ok, so we'll move on to looking at our methods that actually get called when an event occurs. Since we've been dealing with showTagName, lets look at it.

    -showTagName: function(event) +showTagName: function(event) { alert(Event.element(event).tagName); } -

    As you can see, this function accepts one argument--the event. -In order for us to get the element which fired the event we need to +

    As you can see, this function accepts one argument--the event. +In order for us to get the element which fired the event we need to pass that argument to Event.element. Now we can manipulate it at will.

    -

    This covers the most confusing parts of our code. The text above is also -relevant to the remaining parts of our code. If there is anything about +

    This covers the most confusing parts of our code. The text above is also +relevant to the remaining parts of our code. If there is anything about this you don't understand, feel free to ask questions in the forum.

    Removing Event Listeners

    -

    This one threw me for a loop the first time I tried to use it. -I tried something similar to what I did in the Event.observe -call with the exception of using stopObserving, but nothing seemed +

    This one threw me for a loop the first time I tried to use it. +I tried something similar to what I did in the Event.observe +call with the exception of using stopObserving, but nothing seemed to change. In other words, the code below does NOT work.

    -$$(this.list + " li").each(function(item) +$$(this.list + " li").each(function(item) { Event.stopObserving(item, 'click', this.showTagName); }.bind(this)); -

    What's the deal here? The reason this doesn't work is because there +

    What's the deal here? The reason this does not work is because there is no pointer to the observer. This means that when we passed this.showTagName -in the Event.observe method before hand, we passed it as an -anonymous function. We can't reference an anonymous function -because it simply doesn't have a pointer.

    +in the Event.observe method before hand, we passed it as an +anonymous function. We can't reference an anonymous function +because it simply does not have a pointer.

    -

    So how do we get the job done? All we need to do is give the -observing function a pointer, or the jargon free version: Set a variable +

    So how do we get the job done? All we need to do is give the +observing function a pointer, or the jargon free version: Set a variable that points to this.showTagName. Ok, lets change our code a bit.

    this.showTagObserver = this.showTagName.bindEvent(this); -// Observe clicks on our list items -$$(this.list + " li").each(function(item) +// Observe clicks on our list items +$$(this.list + " li").each(function(item) { Event.observe(item, 'click', this.showTagObserver); }.bind(this)); @@ -244,7 +244,7 @@ $$(this.list + " li").each(function(item)

    Now we can remove the event listeners from our list like this:

    -$$(this.list + " li").each(function(item) +$$(this.list + " li").each(function(item) { Event.stopObserving(item, 'click', this.showTagObserver); }.bind(this)); diff --git a/demos/quickstart/protected/pages/Advanced/Scripts3.page b/demos/quickstart/protected/pages/Advanced/Scripts3.page index 123caa3a..8ba6d5c8 100644 --- a/demos/quickstart/protected/pages/Advanced/Scripts3.page +++ b/demos/quickstart/protected/pages/Advanced/Scripts3.page @@ -1,20 +1,20 @@ -

    Javascript in Prado, Questions and Answers

    -

    How do I include the predefined javascript libraries?

    +

    Javascript in PRADO, Questions and Answers

    +

    How do I include the predefined Javascript libraries?

    • Adding libraries in the template <com:TClientScript UsingPradoScripts="effects" /> - -
    • + +
    • Adding libraries in PHP code -$this->getPage()->getClientScript()->registerPradoScript("effects"); +$this->getPage()->getClientScript()->registerPradoScript("effects");
    • -
    -The available packaged libraries included in Prado are + +The available packaged libraries included in Prado are
      -
    • prado : basic prado javascript framework based on Prototype
    • +
    • prado : basic PRADO javascript framework based on Prototype
    • effects : visual effects from script.aculo.us
    • ajax : ajax and callback related based on Prototype
    • validator : validation
    • @@ -24,12 +24,12 @@ The available packaged libraries included in Prado are
    • colorpicker : colorpicker
    -

    The dependencies for each library are automatically resolved. Components +

    The dependencies for each library are automatically resolved. Components that require a particular library will also automatically load the necessary libraries. -For example, if you add a TDatePicker component on the page, the datapicker +For example, if you add a TDatePicker component on the page, the datepicker and its dependencies will be automatically included on the page.

    See TClientScript for options of adding - your custom javascript code to the page.

    + your custom Javascript code to the page.

    \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Advanced/Security.page b/demos/quickstart/protected/pages/Advanced/Security.page index 9be7946a..3dbfe0ab 100644 --- a/demos/quickstart/protected/pages/Advanced/Security.page +++ b/demos/quickstart/protected/pages/Advanced/Security.page @@ -10,7 +10,7 @@ Viewstate lies at the heart of PRADO. Viewstate represents data that can be used It is extremely important to ensure that viewstate is not tampered by end users. Without protection, malicious users may inject harmful code into viewstate and unwanted instructions may be performed when page state is being restored on server side.

    -To prevent viewstate from being tampered, PRADO enforces viewstate HMAC (Keyed-Hashing for Message Authentication) check before restoring viewstate. Such a check can detect if the viewstate has been tampered or not by end users. Should the viewstate is modified, PRADO will stop restoring the viewstate and return an error message. +To prevent viewstate from being tampered, PRADO enforces viewstate HMAC (Keyed-Hashing for Message Authentication) check before restoring viewstate. Such a check can detect if the viewstate has been tampered or not by end users. Should the viewstate is modified, PRADO will stop restoring the viewstate and return an error message.

    HMAC check requires a private key that should be secret to end users. Developers can either manually specify a key or let PRADO automatically generate a key. Manually specified key is useful when the application runs on a server farm. To do so, configure TSecurityManager in application configuration, @@ -31,7 +31,7 @@ HMAC check does not prevent end users from reading the viewstate content. An add Cross site scripting (also known as XSS) occurs when a web application gathers malicious data from a user. Often attackers will inject JavaScript, VBScript, ActiveX, HTML, or Flash into a vulnerable application to fool other application users and gather data from them. For example, a poorly design forum system may display user input in forum posts without any checking. An attacker can then inject a piece of malicious JavaScript code into a post so that when other users read this post, the JavaScript runs unexpectedly on their computers.

    -One of the most important measures to prevent XSS attacks is to check user input before displaying them. One can do HTML-encoding with the user input to achieve this goal. However, in some situations, HTML-encoding may not be preferrable because it disables all HTML tags. +One of the most important measures to prevent XSS attacks is to check user input before displaying them. One can do HTML-encoding with the user input to achieve this goal. However, in some situations, HTML-encoding may not be preferable because it disables all HTML tags.

    PRADO incorporates the work of SafeHTML and provides developers with a useful component called TSafeHtml. By enclosing content within a TSafeHtml component tag, the enclosed content are ensured to be safe to end users. In addition, the commonly used TTextBox has a SafeText property which contains user input that are ensured to be safe if displayed directly to end users. @@ -51,7 +51,7 @@ There are several countermeasures to prevent cookies from being attacked.

  • Validate cookie data and detect if they are altered.
  • -Prado implements a cookie validation scheme that prevents cookies from being modified. In particular, it does HMAC check for the cookie values if cookie validation is enable. +PRADO implements a cookie validation scheme that prevents cookies from being modified. In particular, it does HMAC check for the cookie values if cookie validation is enable.

    Cookie validation is disabled by default. To enable it, configure the THttpRequest module as follows, @@ -62,7 +62,7 @@ Cookie validation is disabled by default. To enable it, configure the THttpR

    -To make use of cookie validation scheme provided by Prado, you also need to retrieve cookies through the Cookies collection of THttpRequest by using the following PHP statements, +To make use of cookie validation scheme provided by PRADO, you also need to retrieve cookies through the Cookies collection of THttpRequest by using the following PHP statements,

    foreach($this->Request->Cookies as $cookie) diff --git a/demos/quickstart/protected/pages/Advanced/State.page b/demos/quickstart/protected/pages/Advanced/State.page index 051090e9..6e7e7f99 100644 --- a/demos/quickstart/protected/pages/Advanced/State.page +++ b/demos/quickstart/protected/pages/Advanced/State.page @@ -7,7 +7,7 @@ Web applications often need to remember what an end user has done in previous pa

    View State

    -View state lies at the heart of PRADO. With view state, Web pages become stateful and are capable of restoring pages to the state that end users interacted with before the current page request. Web programming thus resembles to Windows GUI programming, and developers can think continuously without worrying about the roundtrips between end users and the Web server. For example, with view state, a textbox control is able to detect if the user input changes the content in the textbox. +View state lies at the heart of PRADO. With view state, Web pages become stateful and are capable of restoring pages to the state that end users interacted with before the current page request. Web programming thus resembles to Windows GUI programming, and developers can think continuously without worrying about the round trips between end users and the Web server. For example, with view state, a textbox control is able to detect if the user input changes the content in the textbox.

    View state is only available to controls. View state of a control can be disabled by setting its EnableViewState property to false. To store a variable in view state, call the following, diff --git a/demos/quickstart/protected/pages/Advanced/Themes.page b/demos/quickstart/protected/pages/Advanced/Themes.page index ef593c65..5ba0a121 100644 --- a/demos/quickstart/protected/pages/Advanced/Themes.page +++ b/demos/quickstart/protected/pages/Advanced/Themes.page @@ -4,12 +4,12 @@

    Introduction

    -Themes in Prado provide a way for developers to provide a consistent look-and-feel across an entire web application. A theme contains a list of initial values for properties of various control types. When applying a theme to a page, all controls on that page will receive the corresponding initial property values from the theme. This allows themes to interact with the rich property sets of the various PRADO controls, meaning that themes can be used to specify a large range of presentational properties that other theming methods (e.g. CSS) cannot. For example, themes could be used to specify the default page size of all data grids across an application by specifying a default value for the PageSize property of the TDataGrid control. +Themes in PRADO provide a way for developers to provide a consistent look-and-feel across an entire web application. A theme contains a list of initial values for properties of various control types. When applying a theme to a page, all controls on that page will receive the corresponding initial property values from the theme. This allows themes to interact with the rich property sets of the various PRADO controls, meaning that themes can be used to specify a large range of presentational properties that other theming methods (e.g. CSS) cannot. For example, themes could be used to specify the default page size of all data grids across an application by specifying a default value for the PageSize property of the TDataGrid control.

    Understanding Themes

    -A theme is a directory consists of skin files, javascript files and CSS files. Any javascript or CSS files contained in a theme will be registered with the page that the theme is applied to. A skin is a set of initial property values for a particular control type. A control type may have one or several skins, each identified by a unqiue SkinID. When applying a theme to a page, a skin is applied to a control if the control type and the SkinID value both match to those of the skin. Note, if a skin has an empty SkinID value, it will apply to all controls of the particular type whose SkinID is not set or empty. A skin file consists of one or several skins, for one or several control types. A theme is the union of skins defined in all skin files. +A theme is a directory consists of skin files, javascript files and CSS files. Any javascript or CSS files contained in a theme will be registered with the page that the theme is applied to. A skin is a set of initial property values for a particular control type. A control type may have one or several skins, each identified by a unique SkinID. When applying a theme to a page, a skin is applied to a control if the control type and the SkinID value both match to those of the skin. Note, if a skin has an empty SkinID value, it will apply to all controls of the particular type whose SkinID is not set or empty. A skin file consists of one or several skins, for one or several control types. A theme is the union of skins defined in all skin files.

    Using Themes

    @@ -26,7 +26,7 @@ To use a particular skin in the theme for a control, set SkinID propert This will apply the 'Blue' skin to the button. Note, the initial property values specified by the 'Blue' skin will overwrite any existing property values of the button. Use stylesheet theme if you do not want them to be overwritten. To use stylesheet theme, set the StyleSheetTheme property of the page instead of Theme (you can have both StyleSheetTheme and Theme).

    -To use the javascript files and CSS files contained in a theme, a THead control must be placed on the page template. This is because the theme will register those files with the page and THead is the right place to load those files. +To use the Javascript files and CSS files contained in a theme, a THead control must be placed on the page template. This is because the theme will register those files with the page and THead is the right place to load those files.

    Theme Storage

    @@ -46,7 +46,7 @@ All themes by default must be placed under the [AppEntryPath]/themes di

    Creating Themes

    -Creating a theme involves creating the theme directory and writing skin files (and possibly javascript and CSS files). The name of skin files must be terminated with .skin. The format of skin files are the same as that of control template files. Since skin files do not define parent-child presentational relationship among controls, you cannot place a component tag within another. And any static texts between component tags are discarded. To define the aforementioned 'Blue' skin for TButton, write the following in a skin file, +Creating a theme involves creating the theme directory and writing skin files (and possibly Javascript and CSS files). The name of skin files must be terminated with .skin. The format of skin files are the same as that of control template files. Since skin files do not define parent-child presentational relationship among controls, you cannot place a component tag within another. And any static texts between component tags are discarded. To define the aforementioned 'Blue' skin for TButton, write the following in a skin file,

    <com:TButton SkinID="Blue" BackColor="blue" /> diff --git a/demos/quickstart/protected/pages/Comments.page b/demos/quickstart/protected/pages/Comments.page deleted file mode 100644 index 32c7bcae..00000000 --- a/demos/quickstart/protected/pages/Comments.page +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Comments awaiting approval - - - - <%# $this->DataItem['page'] %> - - DataItem['date_added']) %> /> - - -
    - - <%# $this->DataItem['comment']%> - -
    - - - -
    - - - DataItem['email'] %> /> - DataItem['page'] %> /> - DataItem['comment'] %> TextMode="MultiLine"/> - - - - -
    -
    \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Comments.php b/demos/quickstart/protected/pages/Comments.php deleted file mode 100644 index 7af70ece..00000000 --- a/demos/quickstart/protected/pages/Comments.php +++ /dev/null @@ -1,76 +0,0 @@ - - * @version : $ Sat May 27 20:23:00 AZOST 2006 $ - * @package Demo.Quickstart - * @since 3.0 - */ -class Comments extends TPage -{ - private $_quickstart; - - public function onLoad($param) - { - parent::onLoad($param); - $this->_quickstart = new QuickStartComments; - if(!$this->getIsPostBack()) - $this->refreshData(); - } - - protected function refreshData() - { - $this->comments->setDataSource($this->_quickstart->getQuequedComments()); - $this->comments->dataBind(); - } - - public function approveComment($sender, $param) - { - $ID = $this->comments->DataKeys[$this->comments->SelectedItemIndex]; - $this->_quickstart->approveComment($ID); - $this->refreshData(); - $this->comments->SelectedItemIndex=-1; - } - - public function editComment($sender, $param) - { - $this->comments->SelectedItemIndex=-1; - $this->comments->EditItemIndex=$param->Item->ItemIndex; - $this->refreshData(); - } - - public function cancelEdit($sender, $param) - { - $this->comments->SelectedItemIndex=-1; - $this->comments->EditItemIndex=-1; - $this->refreshData(); - } - - public function deleteComment($sender, $param) - { - $ID = $this->comments->DataKeys[$param->Item->ItemIndex]; - $this->_quickstart->deleteComment($ID); - $this->comments->SelectedItemIndex=-1; - $this->comments->EditItemIndex=-1; - $this->refreshData(); - } - - public function updateComment($sender, $param) - { - $item=$param->Item; - $this->_quickstart->updateComment( - $this->comments->DataKeys[$item->ItemIndex], - $item->page->Text, - $item->email->Text, - $item->content->Text); - - $this->comments->EditItemIndex=-1; - $this->refreshData(); - } -} - -?> \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/ClientScript.page b/demos/quickstart/protected/pages/Controls/ClientScript.page index 5dab41d5..d5687fb1 100644 --- a/demos/quickstart/protected/pages/Controls/ClientScript.page +++ b/demos/quickstart/protected/pages/Controls/ClientScript.page @@ -1,23 +1,23 @@ -

    TClientScript

    +

    TClientScript

    -

    Including Bundled Javascript Libraries in Prado

    +

    Including Bundled Javascript Libraries in Prado

    -TClientScript allows javascript code to be insert or linked to the -page template. Prado is bundled with a large library of javascript functionality -including effects, AJAX, basic event handlers, and many others. The bundled -javascript libraries can be linked to the current page template using the -UsingPradoScripts property. Multiple bundled javascript libraries -can be specified using comma delimited string of the name of javascript library +TClientScript allows Javascript code to be insert or linked to the +page template. PRADO is bundled with a large library of Javascript functionality +including effects, AJAX, basic event handlers, and many others. The bundled +Javascript libraries can be linked to the current page template using the +PradoScripts property. Multiple bundled Javascript libraries +can be specified using comma delimited string of the name of Javascript library to include on the page. For following example will include the "ajax" and "effects" library.

    -<com:TClientScript UsingPradoScripts="ajax, effects" /> +<com:TClientScript PradoScripts="ajax, effects" />

    - The available bundled libraries included in Prado are + The available bundled libraries included in Prado are

    • prado : basic prado javascript framework based on Prototype
    • effects : visual effects from script.aculo.us
    • @@ -29,12 +29,12 @@ to include on the page. For following example will include the "ajax" and "effec
    • colorpicker : colorpicker

    -

    The dependencies for each library are automatically resolved. That is, +

    The dependencies for each library are automatically resolved. That is, specifying, say the "ajax", will also include the "prado" library.

    - -

    Including Custom Javascript Files

    -

    Custom javascript files can be register using the ScriptUrl property. -The following example includes the javascript file "test.js" to the page. In this case, the file + +

    Including Custom Javascript Files

    +

    Custom Javascript files can be register using the ScriptUrl property. +The following example includes the Javascript file "test.js" to the page. In this case, the file "test.js" is relative the current template you are using. Since the property value is dynamic asset tag, the file "test.js" will be published automatically, that is, the file will be copied to the assets directory if necessary. @@ -42,11 +42,11 @@ automatically, that is, the file will be copied to the assets directory if neces <com:TClientScript ScriptUrl=<%~ test.js %> /> -

    You can include javascript files from other servers by specifying the full URL string in +

    You can include Javascript files from other servers by specifying the full URL string in the ScriptUrl property.

    -

    Including Custom Javascript Code Blocks

    +

    Including Custom Javascript Code Blocks

    Any content within the TClientScript control tag will be considered as - javascript code and will be rendered where it is declared.

    + Javascript code and will be rendered where it is declared.

    \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/DataGrid.page b/demos/quickstart/protected/pages/Controls/DataGrid.page index ed1ad32b..392b78ab 100644 --- a/demos/quickstart/protected/pages/Controls/DataGrid.page +++ b/demos/quickstart/protected/pages/Controls/DataGrid.page @@ -6,15 +6,15 @@ TDatagrid is an important control in building complex Web applications. It displays data in a tabular format with rows (also called items) and columns. A row is composed by cells, while columns govern how cells should be displayed according to their association with the columns. Data specified via DataSource or DataSourceID are bound to the rows and feed contents to cells.

    -TDataGrid is highly interactive. Users can sort the data along specified columns, navigate through different pages of the data, and perform actions, such as editting and deleting, on rows of the data. +TDataGrid is highly interactive. Users can sort the data along specified columns, navigate through different pages of the data, and perform actions, such as editing and deleting, on rows of the data.

    -Rows of TDataGrid can be accessed via its Items property. A row (item) can be in one of several modes: browsing, editting and selecting, which affects how cells in the row are displayed. To change an item's mode, modify EditItemIndex or SelectedItemIndex. Note, if an item is in edit mode, then selecting this item will have no effect. +Rows of TDataGrid can be accessed via its Items property. A row (item) can be in one of several modes: browsing, editing and selecting, which affects how cells in the row are displayed. To change an item's mode, modify EditItemIndex or SelectedItemIndex. Note, if an item is in edit mode, then selecting this item will have no effect.

    Columns

    -Columns of a data grid determine how the associated cells are displayed. For example, cells associated with a TBoundColumn are displayed differently according to their modes. A cell is displayed as a static text if the cell is in browsing mode, a text box if it is in editting mode, and so on. +Columns of a data grid determine how the associated cells are displayed. For example, cells associated with a TBoundColumn are displayed differently according to their modes. A cell is displayed as a static text if the cell is in browsing mode, a text box if it is in editing mode, and so on.

    PRADO provides five types of columns: @@ -103,7 +103,7 @@ The following example uses manually specified columns to show a list of book inf

  • Publisher - displayed as a piece of text using TBoundColumn.
  • Price - displayed as a piece of text using TBoundColumn with output formatting string and customized styles.
  • In-stock or not - displayed as a checkbox using TCheckBoxColumn.
  • -
  • Rating - displayed as an image using TTemplateColumn which allows maximum freedom in specifiying cell contents.
  • +
  • Rating - displayed as an image using TTemplateColumn which allows maximum freedom in specifying cell contents.
  • Pay attention to how item (row) styles and column styles cooperate together to affect the appearance of the cells in the datagrid.

    @@ -111,7 +111,7 @@ The following example uses manually specified columns to show a list of book inf

    Interacting with TDataGrid

    -Besides the rich data presentation functionalities as demonstrated in previous section, TDataGrid is also highly user interactive. An import usage of TDataGrid is editting or deleting rows of data. The TBoundColumn can adjust the associated cell presentation according to the mode of datagrid items. When an item is in browsing mode, the cell is displayed with a static text; when the item is in editting mode, a textbox is displayed to collect user inputs. TDataGrid provides TEditCommandColumn for switching item modes. In addition, TButtonColumn offers developers the flexibility of creating arbitrary buttons for various user interactions. +Besides the rich data presentation functionalities as demonstrated in previous section, TDataGrid is also highly user interactive. An import usage of TDataGrid is editing or deleting rows of data. The TBoundColumn can adjust the associated cell presentation according to the mode of datagrid items. When an item is in browsing mode, the cell is displayed with a static text; when the item is in editing mode, a textbox is displayed to collect user inputs. TDataGrid provides TEditCommandColumn for switching item modes. In addition, TButtonColumn offers developers the flexibility of creating arbitrary buttons for various user interactions.

    The following example shows how to make the previous book information table an interactive one. It allows users to edit and delete book items from the table. Two additional columns are used in the example to allow users interact with the datagrid: TEditCommandColumn and TButtonColumn. diff --git a/demos/quickstart/protected/pages/Controls/DataList.page b/demos/quickstart/protected/pages/Controls/DataList.page index c1ff1147..75541564 100644 --- a/demos/quickstart/protected/pages/Controls/DataList.page +++ b/demos/quickstart/protected/pages/Controls/DataList.page @@ -50,7 +50,7 @@ The following example shows how to use TDataList to display tabular data, with d

    -A common use of TDataList is for maintaining tabular data, including browsing, editting, deleting data items. This is enabled by the command events and various item templates of TDataList. +A common use of TDataList is for maintaining tabular data, including browsing, editing, deleting data items. This is enabled by the command events and various item templates of TDataList.

    The following example displays a computer product information. Users can add new products, modify or delete existing ones. In order to locate the data item for updating or deleting, DataKeys property is used. diff --git a/demos/quickstart/protected/pages/Controls/DatePicker.page b/demos/quickstart/protected/pages/Controls/DatePicker.page index a37ff048..36d2f435 100644 --- a/demos/quickstart/protected/pages/Controls/DatePicker.page +++ b/demos/quickstart/protected/pages/Controls/DatePicker.page @@ -3,14 +3,14 @@

    TDatePicker

    -

    TDatePicker displays a text box for date input purpose. +

    TDatePicker displays a text box for date input purpose. When the text box receives focus, a calendar will pop up and users can pick up from it a date that will be automatically entered into the text box. The format of the date string displayed in the text box is determined by the DateFormat property. Valid formats are the combination of the following tokens: - - + + Character Format Pattern (en-US) --------------------------------------------------------------------- d day digit @@ -32,7 +32,7 @@ only accepts integers such as the Unix timestamp.

    -TDatePicker has three Mode to show the date picker popup. +TDatePicker has three Mode to show the date picker popup.

    • Basic - Only shows a text input, focusing on the input shows the date picker.
    • Button - Shows a button next to the text input, clicking on the button shows the date, button text can be by the ButtonText property.
    • @@ -40,8 +40,8 @@ TDatePicker has three Mode to show the date picker popup.

    -

    The CssClass property can be used to override the css class name -for the date picker panel. The CalendarStyle property changes the overall calendar style. +

    The CssClass property can be used to override the CSS class name +for the date picker panel. The CalendarStyle property changes the overall calendar style. The following CalendarStyle values are available:

    • default - The default calendar style.
    • @@ -50,9 +50,9 @@ The following CalendarStyle values are available:

      The InputMode property can be set to "TextBox" or "DropDownList" with default as "TextBox". In DropDownList mode, in addition to the popup date picker, three -drop down list (day, month and year) are presented to select the date . +drop down list (day, month and year) are presented to select the date . When InputMode equals "DropDownList", the order and appearance of the date, month, and year -will depend on the pattern specified in DateFormat property. +will depend on the pattern specified in DateFormat property.

      The popup date picker can be hidden by specifying ShowCalendar as false. Much of the @@ -64,20 +64,20 @@ where FromYear is the starting year and UpToYear is the last y The starting day of the week can be changed by the FirstDayOfWeek property, with 0 as Sunday, 1 as Monday, etc.

      -

      Note 1: If the InputMode is "TextBox", the DateFormat should -only NOT contain MMM or MMMM patterns. The +

      Note 1: If the InputMode is "TextBox", the DateFormat should +only NOT contain MMM or MMMM patterns. The server side date parser will not be able to determine the correct date if MMM or MMMM are used. When InputMode equals "DropDownList", all patterns can be used.

      - +

      Note 2: When the TDatePicker is used together -with a validator, the DateFormat property of the validator must be equal to -the DateFormat of the TDatePicker AND must set DataType="Date" -on the validator to ensure correct validation. See +with a validator, the DateFormat property of the validator must be equal to +the DateFormat of the TDatePicker AND must set DataType="Date" +on the validator to ensure correct validation. See TCompareValidator, -TDataTypeValidator and -TRangeValidator +TDataTypeValidator and +TRangeValidator for details.

      - + \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/HtmlArea.page b/demos/quickstart/protected/pages/Controls/HtmlArea.page index 00a65573..e40a4444 100644 --- a/demos/quickstart/protected/pages/Controls/HtmlArea.page +++ b/demos/quickstart/protected/pages/Controls/HtmlArea.page @@ -24,7 +24,7 @@ The default editor gives only the basic tool bar. To change or add additional to

      -The client-side visual editting capability is supported by Internet Explorer 5.0+ for Windows and Gecko-based browser. If the browser does not support the visual editting, a traditional textarea will be displayed. +The client-side visual editing capability is supported by Internet Explorer 5.0+ for Windows and Gecko-based browser. If the browser does not support the visual editing, a traditional textarea will be displayed.

      @@ -39,7 +39,7 @@ Firefox 1.5b2           OK              OK
       Safari 2.0 (412)                        OK(1)
       Opera 9 Preview 1       OK(1)           OK(1)
       ----------------------------------------------------
      -(1) - Partialy working
      +(1) - Partially working
       ----------------------------------------------------
       
      diff --git a/demos/quickstart/protected/pages/Controls/JavascriptLogger.page b/demos/quickstart/protected/pages/Controls/JavascriptLogger.page index ccb4a27b..2c0032f8 100644 --- a/demos/quickstart/protected/pages/Controls/JavascriptLogger.page +++ b/demos/quickstart/protected/pages/Controls/JavascriptLogger.page @@ -4,7 +4,7 @@

      -TJavascriptLogger provides logging for client-side javascript. It is mainly a wrapper of the javascript developed at http://gleepglop.com/javascripts/logger/. +TJavascriptLogger provides logging for client-side javascript. It is mainly a wrapper of the Javascript developed at http://gleepglop.com/javascripts/logger/.

      @@ -15,7 +15,7 @@ To use TJavascriptLogger, simply place the following component tag in a

      -Then, the client-side javascript may contain the following statements. When they are executed, they will appear in the logger window. +Then, the client-side Javascript may contain the following statements. When they are executed, they will appear in the logger window.

      Logger.info('something happend'); diff --git a/demos/quickstart/protected/pages/Controls/NewControl.page b/demos/quickstart/protected/pages/Controls/NewControl.page index 54465ff7..5662f848 100644 --- a/demos/quickstart/protected/pages/Controls/NewControl.page +++ b/demos/quickstart/protected/pages/Controls/NewControl.page @@ -13,7 +13,7 @@ In general, there are two ways of writing new controls: composition of existing Composition is the easiest way of creating new controls. It mainly involves instantiating existing controls, configuring them and making them the constituent components. The properties of the constituent components are exposed through subproperties.

      -One can compose a new control in two ways. One is to extend TCompositeControl and override the TControl::createChildControls() method. The other is to extend TTemplateControl (or its child classes) and write a control template. The latter is easier to use and can organize the layout constituent compoents more intuitively, while the former is more efficient because it does not require parsing of the template. +One can compose a new control in two ways. One is to extend TCompositeControl and override the TControl::createChildControls() method. The other is to extend TTemplateControl (or its child classes) and write a control template. The latter is easier to use and can organize the layout constituent components more intuitively, while the former is more efficient because it does not require parsing of the template.

      As an example, we show how to create a labeled textbox called LabeledTextBox using the above two approaches. A labeled textbox displays a label besides a textbox. We want reuse the PRADO provided TLabel and TTextBox to accomplish this task. @@ -130,8 +130,8 @@ Other important properties and methods include: TWebControl is mainly used as a base class for controls representing HTML elements. It provides a set of properties that are common among HTML elements. It breaks the TControl::render() into the following methods that are more suitable for rendering an HTML element:

        -
      • addAttributesToRender() - adds attributes for the HTML element to be rendered. This method is often overriden by derived classes as they usually have different attributes to be rendered.
      • -
      • renderBeginTag() - renders the openning HTML tag.
      • +
      • addAttributesToRender() - adds attributes for the HTML element to be rendered. This method is often overridden by derived classes as they usually have different attributes to be rendered.
      • +
      • renderBeginTag() - renders the opening HTML tag.
      • renderContents() - renders the content enclosed within the HTML element. By default, it displays the items in the Controls collection of the control. Derived classes may override this method to render customized contents.
      • renderEndTag() - renders the closing HTML tag.
      diff --git a/demos/quickstart/protected/pages/Controls/Panel.page b/demos/quickstart/protected/pages/Controls/Panel.page index d507da36..be36095b 100644 --- a/demos/quickstart/protected/pages/Controls/Panel.page +++ b/demos/quickstart/protected/pages/Controls/Panel.page @@ -4,7 +4,7 @@

      -TPanel acts as a presentational container for other control. It displays a <div> element on a page. The property Wrap specifies whether the panel's body content should wrap or not, while HorizontalAlign governs how the content is aligned horizontally and Direction indicates the content direction (left to right or right to left). You can set BackImageUrl to give a background image to the panel, and you can ste GroupingText so that the panel is displayed as a field set with a legend text. Finally, you can specify a default button to be fired when users press 'return' key within the panel by setting the DefaultButton property. +TPanel acts as a presentational container for other control. It displays a <div> element on a page. The property Wrap specifies whether the panel's body content should wrap or not, while HorizontalAlign governs how the content is aligned horizontally and Direction indicates the content direction (left to right or right to left). You can set BackImageUrl to give a background image to the panel, and you can set GroupingText so that the panel is displayed as a field set with a legend text. Finally, you can specify a default button to be fired when users press 'return' key within the panel by setting the DefaultButton property.

      diff --git a/demos/quickstart/protected/pages/Controls/Repeater.page b/demos/quickstart/protected/pages/Controls/Repeater.page index 795e27aa..d8f3fd0a 100644 --- a/demos/quickstart/protected/pages/Controls/Repeater.page +++ b/demos/quickstart/protected/pages/Controls/Repeater.page @@ -5,7 +5,7 @@ TRepeater displays its content defined in templates repeatedly based on the given data specified by the DataSource or DataSourceID property. The repeated contents can be retrieved from the Items property. Each item is created by instantiating a template and each is a child control of the repeater.

      -Like normal control templates, the repeater templates can contain static text, controls and special tags, which after instantiation, become child contents of the corresponding item. TRepeater defiens five templates for different purposes, +Like normal control templates, the repeater templates can contain static text, controls and special tags, which after instantiation, become child contents of the corresponding item. TRepeater defines five templates for different purposes,

      • HeaderTemplate - the template used for displaying content at the beginning of a repeater;
      • diff --git a/demos/quickstart/protected/pages/Controls/Validation.page b/demos/quickstart/protected/pages/Controls/Validation.page index a28c8faf..12836b8c 100644 --- a/demos/quickstart/protected/pages/Controls/Validation.page +++ b/demos/quickstart/protected/pages/Controls/Validation.page @@ -26,7 +26,7 @@ Validators share a common set of properties, which are defined in the base class
      • Dynamic - the space for displaying the error message is NOT reserved. Therefore, showing up the error message will shift the layout of your page around (usually down).
      -
    • ControlCssClass - the Css class that is applied to the control being validated in case the validation fails.
    • +
    • ControlCssClass - the CSS class that is applied to the control being validated in case the validation fails.
    • FocusOnError - set focus at the validating place if the validation fails. Defaults to false.
    • FocusElementID - the ID of the HTML element that will receive focus if validation fails and FocusOnError is true.
    @@ -71,7 +71,7 @@ TEmailAddressValidator verifies that the user input is a valid email address. Th Note, if the input being validated is empty, TEmailAddressValidator will not do validation. Use a TRequiredFieldValidator to ensure the value is not empty.

    - +

    TCompareValidator

    diff --git a/demos/quickstart/protected/pages/Controls/Wizard.page b/demos/quickstart/protected/pages/Controls/Wizard.page index ce8bc774..fe910aa7 100644 --- a/demos/quickstart/protected/pages/Controls/Wizard.page +++ b/demos/quickstart/protected/pages/Controls/Wizard.page @@ -74,15 +74,15 @@ In this sample, we use wizard to collect user's preference of color. In the firs Given a set of wizard steps, TWizard supports three different ways of navigation among them:

      -
    • Bidirectional - Users can navigate forward and backward along a sequence of wizard steps. User input data is usally collected at the last step. This is also known as commit-at-the-end model. It is the default navigation way that TWizard supports.
    • -
    • Unidirectional - Users can navigate forward along a sequence of wizard steps. Backward move is not allowed. User input data is usally collected step by step. This is also known as command-as-you-go model. To disallow backward move to a specific step, set its AllowReturn property to false.
    • +
    • Bidirectional - Users can navigate forward and backward along a sequence of wizard steps. User input data is usually collected at the last step. This is also known as commit-at-the-end model. It is the default navigation way that TWizard supports.
    • +
    • Unidirectional - Users can navigate forward along a sequence of wizard steps. Backward move is not allowed. User input data is usually collected step by step. This is also known as command-as-you-go model. To disallow backward move to a specific step, set its AllowReturn property to false.
    • Nonlinear - User input in a step determines which step to go next. To do so, set ActiveStepIndex to the step that you want the user to go to. In this case, when a user clicks on the previous button in the navigation panel, the previous step that they visited (not the previous step in the sequential order) will become visible.

    Using Templates in Wizard

    -TWizard supports more concrete control of its outlook through templating. In particular, it provides the following template properties that allow complete customization of the wizard's header, navigation and side bar. +TWizard supports more concrete control of its outlook through templates. In particular, it provides the following template properties that allow complete customization of the wizard's header, navigation and side bar.

    @@ -266,12 +282,12 @@ class LogInTest extends WebTestCase { $this->get('http://www.my-site.com/login.php'); $this->setField('u', 'Me'); $this->setField('p', 'Secret'); - $this->clickSubmit('Log in'); - $this->assertWantedPattern('/Welcome Me/'); + $this->click('Log in'); + $this->assertText('Welcome Me'); $this->restart(); $this->get('http://www.my-site.com/restricted.php'); - $this->assertWantedPattern('/Access denied/'); + $this->assertText('Access denied'); } } @@ -297,13 +313,13 @@ class LogInTest extends WebTestCase { $this->get('http://www.my-site.com/login.php'); $this->setField('u', 'Me'); $this->setField('p', 'Secret'); - $this->clickSubmit('Log in'); - $this->assertWantedPattern('/Welcome Me/'); + $this->click('Log in'); + $this->assertText('Welcome Me'); $this->ageCookies(3600); $this->restart(); $this->get('http://www.my-site.com/restricted.php'); - $this->assertWantedPattern('/Access denied/'); + $this->assertText('Access denied'); } } diff --git a/tests/test_tools/simpletest/docs/en/browser_documentation.html b/tests/test_tools/simpletest/docs/en/browser_documentation.html index ef54aaea..f6046a9d 100755 --- a/tests/test_tools/simpletest/docs/en/browser_documentation.html +++ b/tests/test_tools/simpletest/docs/en/browser_documentation.html @@ -21,9 +21,6 @@ Group tests
  • -Server stubs -
  • -
  • Mock objects
  • @@ -76,9 +73,9 @@ $browser = &new SimpleBrowser(); $browser->get('http://php.net/'); - $browser->clickLink('reporting bugs'); - $browser->clickLink('statistics'); - $page = $browser->clickLink('PHP 5 bugs only'); + $browser->click('reporting bugs'); + $browser->click('statistics'); + $page = $browser->click('PHP 5 bugs only'); preg_match('/status=Open.*?by=Any.*?(\d+)<\/a>/', $page, $matches); print $matches[1]; ?> @@ -297,14 +294,22 @@ useFrames()Enables frames support + + +ignoreCookies()Disables sending and receiving of cookies + + +useCookies()Enables cookie support The methods SimpleBrowser::setConnectionTimeout() SimpleBrowser::setMaximumRedirects(), SimpleBrowser::setMaximumNestedFrames(), - SimpleBrowser::ignoreFrames() and - SimpleBrowser::useFrames() continue to apply + SimpleBrowser::ignoreFrames(), + SimpleBrowser::useFrames(), + SimpleBrowser::ignoreCookies() and + SimpleBrowser::useCokies() continue to apply to every subsequent request. The other methods are frames aware. This means that if you have an individual frame that is not @@ -332,7 +337,7 @@ class TestOfRegistration extends UnitTestCase { $browser->get('http://my-site.com/register.php'); $browser->setField('email', 'me@here'); $browser->setField('password', 'Secret'); - $browser->clickSubmit('Register'); + $browser->click('Register'); $authenticator = &new Authenticator(); $member = &$authenticator->findByEmail('me@here'); @@ -361,14 +366,14 @@ class TestOfSecurity extends UnitTestCase { $first->get('http://my-site.com/login.php'); $first->setField('name', 'Me'); $first->setField('password', 'Secret'); - $first->clickSubmit('Enter'); + $first->click('Enter'); $this->assertEqual($first->getTitle(), 'Welcome'); $second = &new SimpleBrowser(); $second->get('http://my-site.com/login.php'); $second->setField('name', 'Me'); $second->setField('password', 'Secret'); - $second->clickSubmit('Enter'); + $second->click('Enter'); $this->assertEqual($second->getTitle(), 'Access Denied'); } } diff --git a/tests/test_tools/simpletest/docs/en/expectation_documentation.html b/tests/test_tools/simpletest/docs/en/expectation_documentation.html index 0165988c..bd189b94 100755 --- a/tests/test_tools/simpletest/docs/en/expectation_documentation.html +++ b/tests/test_tools/simpletest/docs/en/expectation_documentation.html @@ -23,9 +23,6 @@ Group tests
  • -Server stubs -
  • -
  • Mock objects
  • @@ -101,8 +98,8 @@
     class TestOfNewsService extends UnitTestCase {
         ...
    -    function testConnectionFailure() {
    -        $writer = &new MockWriter($this);
    +    function testConnectionFailure() {<strong>
    +        $writer = &new MockWriter();
             $writer->expectOnce('write', array(
                     'Cannot connect to news service ' .
                     '"BBC News" at this time. ' .
    @@ -110,8 +107,6 @@ class TestOfNewsService extends UnitTestCase {
             
             $service = &new NewsService('BBC News');
             $service->publish($writer);
    -        
    -        $writer->tally();
         }
     }
     
    @@ -129,15 +124,13 @@ class TestOfNewsService extends UnitTestCase { class TestOfNewsService extends UnitTestCase { ... function testConnectionFailure() { - $writer = &new MockWriter($this); + $writer = &new MockWriter(); $writer->expectOnce( 'write', - array(new WantedPatternExpectation('/cannot connect/i'))); + array(new PatternExpectation('/cannot connect/i'))); $service = &new NewsService('BBC News'); $service->publish($writer); - - $writer->tally(); } } @@ -179,10 +172,10 @@ class TestOfNewsService extends UnitTestCase { NotIndenticalExpectationInverts the mock object logic -WantedPatternExpectationUses a Perl Regex to match a string +PatternExpectationUses a Perl Regex to match a string -NoUnwantedPatternExpectationPasses only if failing a Perl Regex +NoPatternExpectationPasses only if failing a Perl Regex IsAExpectationChecks the type or class name only @@ -208,29 +201,26 @@ class TestOfNewsService extends UnitTestCase {

    The expectation classes can be used not just for sending assertions - from mock objects, but also for selecting behaviour for either - the - mock objects - or the - server stubs. + from mock objects, but also for selecting behaviour for the + mock objects. Anywhere a list of arguments is given, a list of expectation objects can be inserted instead.

    - Suppose we want an authorisation server stub to simulate a successful login + Suppose we want an authorisation server mock to simulate a successful login only if it receives a valid session object. We can do this as follows...

    -Stub::generate('Authorisation');
    +Mock::generate('Authorisation');
     
    -$authorisation = new StubAuthorisation();
    +$authorisation = new MockAuthorisation();
     $authorisation->setReturnValue(
             'isAllowed',
             true,
             array(new IsAExpectation('Session', 'Must be a session')));
     $authorisation->setReturnValue('isAllowed', false);
     
    - We have set the default stub behaviour to return false when + We have set the default mock behaviour to return false when isAllowed is called. When we call the method with a single parameter that is a Session object, it will return true. @@ -299,14 +289,14 @@ $authorisation->setReturnValue('isAllowed', false);

    The most crude way of doing this is to use the - SimpleTest::assertExpectation() method to + SimpleTest::assert() method to test against it directly...

     class TestOfNetworking extends UnitTestCase {
         ...
         function testGetValidIp() {
             $server = &new Server();
    -        $this->assertExpectation(
    +        $this->assert(
                     new ValidIp(),
                     $server->getIp(),
                     'Server IP address->%s');
    @@ -327,7 +317,7 @@ $authorisation->setReturnValue('isAllowed', false);
     class TestOfNetworking extends UnitTestCase {
         ...
         function assertValidIp($ip, $message = '%s') {
    -        $this->assertExpectation(new ValidIp(), $ip, $message);
    +        $this->assert(new ValidIp(), $ip, $message);
         }
         
         function testGetValidIp() {
    diff --git a/tests/test_tools/simpletest/docs/en/form_testing_documentation.html b/tests/test_tools/simpletest/docs/en/form_testing_documentation.html
    index b1e15b3d..50b634c0 100755
    --- a/tests/test_tools/simpletest/docs/en/form_testing_documentation.html
    +++ b/tests/test_tools/simpletest/docs/en/form_testing_documentation.html
    @@ -21,9 +21,6 @@
     Group tests
     
  • -Server stubs -
  • -
  • Mock objects
  • @@ -96,7 +93,9 @@ class SimpleFormTests extends WebTestCase { their default values just as they would appear in the web browser. The assertion tests that a HTML widget exists in the page with the name "a" and that it is currently set to the value - "A default" + "A default". + As usual, we could use a pattern expectation instead if a fixed + string.

    We could submit the form straight away, but first we'll change @@ -108,7 +107,7 @@ class SimpleFormTests extends WebTestCase { $this->get('http://www.my-site.com/'); $this->assertField('a', 'A default'); $this->setField('a', 'New value'); - $this->clickSubmit('Go'); + $this->click('Go'); } } @@ -235,7 +234,7 @@ class SimpleFormTests extends WebTestCase { $this->get('http://www.lastcraft.com/form_testing_documentation.php'); $this->assertField('crud', array('c', 'r', 'u', 'd')); $this->setField('crud', array('r')); - $this->clickSubmit('Enable Privileges'); + $this->click('Enable Privileges'); } } @@ -261,7 +260,7 @@ class SimpleFormTests extends WebTestCase { $this->post( 'http://www.my-site.com/add_user.php', array('type' => 'superuser')); - $this->assertNoUnwantedPattern('/user created/i'); + $this->assertNoText('user created'); } } diff --git a/tests/test_tools/simpletest/docs/en/group_test_documentation.html b/tests/test_tools/simpletest/docs/en/group_test_documentation.html index adbc66ef..1e14d31e 100755 --- a/tests/test_tools/simpletest/docs/en/group_test_documentation.html +++ b/tests/test_tools/simpletest/docs/en/group_test_documentation.html @@ -21,9 +21,6 @@ Group tests

  • -Server stubs -
  • -
  • Mock objects
  • @@ -87,7 +84,7 @@ class MyFileTestCase extends UnitTestCase { ... } - SimpleTestOptions::ignore('MyFileTestCase'); + SimpleTest::ignore('MyFileTestCase'); class FileTester extends MyFileTestCase { ... diff --git a/tests/test_tools/simpletest/docs/en/index.html b/tests/test_tools/simpletest/docs/en/index.html index 04797272..c7183c49 100755 --- a/tests/test_tools/simpletest/docs/en/index.html +++ b/tests/test_tools/simpletest/docs/en/index.html @@ -24,9 +24,6 @@ Group tests
  • -Server stubs -
  • -
  • Mock objects
  • @@ -101,26 +98,22 @@ We start by creating a test script which we will call tests/log_test.php and populate it as follows...
    -<?php
    -require_once('simpletest/unit_tester.php');
    -require_once('simpletest/reporter.php');
    -require_once('../classes/log.php');
    -?>
    -
    - Here the simpletest folder is either local or in the path. - You would have to edit these locations depending on where you - placed the toolset. - Next we create a test case... -
    -<?php
    +<?php
     require_once('simpletest/unit_tester.php');
     require_once('simpletest/reporter.php');
     require_once('../classes/log.php');
    -
    +
     class TestOfLogging extends UnitTestCase {
     }
     ?>
     
    + Here the simpletest folder is either local or in the path. + You would have to edit these locations depending on where you + placed the toolset. + The TestOfLogging is our frst test case and it's + currently empty. +

    +

    Now we have five lines of scaffolding code and still no tests. However from this part on we get return on our investment very quickly. We'll assume that the Log class @@ -376,21 +369,15 @@ Mock::generate('Log'); class TestOfSessionLogging extends UnitTestCase { function testLogInIsLogged() { - $log = &new MockLog($this); + $log = &new MockLog(); $log->expectOnce('message', array('User fred logged in.')); $session_pool = &new SessionPool($log); - $session_pool->logIn('fred'); - $log->tally(); + $session_pool->logIn('fred'); } } ?> - The tally() call is needed to - tell the mock object that time is up for the expected call - count. - Without it the mock would wait forever for the method - call to come in without ever actually notifying the test case. - The other test will be triggered when the call to + The test will be triggered when the call to message() is invoked on the MockLog object. The mock call will trigger a parameter comparison and then send the @@ -398,6 +385,13 @@ class TestOfSessionLogging extends UnitTestCase { Wildcards can be included here too so as to prevent tests becoming too specific.

    +

    + If the mock reaches the end of the test case without the + method being called, the expectOnce() + expectation will trigger a test failure. + In other words the mocks can detect the absence of + behaviour as well as the presence. +

    The mock objects in the SimpleTest suite can have arbitrary return values set, sequences of returns, return values @@ -439,12 +433,12 @@ class TestOfAbout extends WebTestCase { function setUp() { $this->get('http://test-server/index.php'); - $this->clickLink('About'); + $this->click('About'); } function testSearchEngineOptimisations() { $this->assertTitle('A long title about us for search engines'); - $this->assertWantedPattern('/a popular keyphrase/i'); + $this->assertPattern('/a popular keyphrase/i'); } } $test = &new TestOfAbout(); diff --git a/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html b/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html index 2f8a1f90..eb32c619 100755 --- a/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html +++ b/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html @@ -21,9 +21,6 @@ Group tests

  • -Server stubs -
  • -
  • Mock objects
  • @@ -76,7 +73,17 @@

    If mock objects only behaved as actors they would simply be - known as server stubs. + known as server stubs. + This was originally a pattern named by Robert Binder (Testing + object-oriented systems: models, patterns, and tools, + Addison-Wesley) in 1999. +

    +

    + A server stub is a simulation of an object or component. + It should exactly replace a component in a system for test + or prototyping purposes, but remain lightweight. + This allows tests to run more quickly, or if the simulated + class has not been written, to run at all.

    However, the mock objects not only play a part (by supplying chosen @@ -87,6 +94,8 @@ If expectations are not met they save us the effort of writing a failed test assertion by performing that duty on our behalf. +

    +

    In the case of an imaginary database connection they can test that the query, say SQL, was correctly formed by the object that is using the connection. @@ -138,7 +147,7 @@ Mock::generate('DatabaseConnection'); class MyTestCase extends UnitTestCase { function testSomething() { - $connection = &new MockDatabaseConnection($this); + $connection = &new MockDatabaseConnection(); } } @@ -155,19 +164,27 @@ class MyTestCase extends UnitTestCase {

    - The mock version of a class has all the methods of the original + The mock version of a class has all the methods of the original, so that operations like $connection->query() are still legal. - As with stubs we can replace the default null return values... + The return value will be null, + but we can change that with...

    -$connection->setReturnValue('query', 37);
    +$connection->setReturnValue('query', 37)
     
    Now every time we call $connection->query() we get the result of 37. - As with the stubs we can set wildcards and we can overload the - wildcard parameter. + We can set the return value to anything, say a hash of + imaginary database results or a list of persistent objects. + Parameters are irrelevant here, we always get the same + values back each time once they have been set up this way. + That may not sound like a convincing replica of a + database connection, but for the half a dozen lines of + a test method it is usually all you need. +

    +

    We can also add extra methods to the mock when generating it and choose our own class name...

    @@ -180,8 +197,12 @@ class MyTestCase extends UnitTestCase {
                     You can create a special mock to simulate this situation.
                 

    - All of the patterns available with server stubs are available - to mock objects... + Things aren't always that simple though. + One common problem is iterators, where constantly returning + the same value could cause an endless loop in the object + being tested. + For these we need to set up sequences of values. + Let's say we have a simple iterator that looks like this...

     class Iterator {
         function Iterator() {
    @@ -191,7 +212,8 @@ class Iterator {
         }
     }
     
    - Again, assuming that this iterator only returns text until it + This is about the simplest iterator you could have. + Assuming that this iterator only returns text until it reaches the end, when it returns false, we can simulate it with...
    @@ -200,7 +222,7 @@ Mock::generate('Iterator');
     class IteratorTest extends UnitTestCase() {
         
         function testASequence() {
    -        $iterator = &new MockIterator($this);
    +        $iterator = &new MockIterator();
             $iterator->setReturnValue('next', false);
             $iterator->setReturnValueAt(0, 'next', 'First string');
             $iterator->setReturnValueAt(1, 'next', 'Second string');
    @@ -218,7 +240,10 @@ class IteratorTest extends UnitTestCase() {
                     The constant one is a kind of default if you like.
                 

    - A repeat of the stubbed information holder with name/value pairs... + Another tricky situation is an overloaded + get() operation. + An example of this is an information holder with name/value pairs. + Say we have a configuration class like...

     class Configuration {
         function Configuration() {
    @@ -237,7 +262,7 @@ class Configuration {
                     we want different results for different keys.
                     Luckily the mocks have a filter system...
     
    -$config = &new MockConfiguration($this);
    +$config = &new MockConfiguration();
     $config->setReturnValue('getValue', 'primary', array('db_host'));
     $config->setReturnValue('getValue', 'admin', array('db_user'));
     $config->setReturnValue('getValue', 'secret', array('db_password'));
    @@ -257,10 +282,36 @@ $config->getValue('db_user')
                     to its list of returns one after another until
                     a complete match is found.
                 

    +

    + You can set a default argument argument like so... +

    +
    +$config->setReturnValue('getValue', false, array('*'));
    +
    + This is not the same as setting the return value without + any argument requirements like this... +
    +
    +$config->setReturnValue('getValue', false);
    +
    + In the first case it will accept any single argument, + but exactly one is required. + In the second case any number of arguments will do and + it acts as a catchall after all other matches. + Note that if we add further single parameter options after + the wildcard in the first case, they will be ignored as the wildcard + will match first. + With complex parameter lists the ordering could be important + or else desired matches could be masked by earlier wildcard + ones. + Declare the most specific matches first if you are not sure. +

    There are times when you want a specific object to be dished out by the mock rather than a copy. - Again this is identical to the server stubs mechanism... + The PHP4 copy semantics force us to use a different method + for this. + You might be simulating a container for example...

     class Thing {
     }
    @@ -276,14 +327,79 @@ class Vector {
                     In this case you can set a reference into the mock's
                     return list...
     
    -$thing = new Thing();
    -$vector = &new MockVector($this);
    +$thing = &new Thing();
    +$vector = &new MockVector();
     $vector->setReturnReference('get', $thing, array(12));
     
    With this arrangement you know that every time $vector->get(12) is called it will return the same $thing each time. + This is compatible with PHP5 as well. +

    +

    + These three factors, timing, parameters and whether to copy, + can be combined orthogonally. + For example... +

    +$complex = &new MockComplexThing();
    +$stuff = &new Stuff();
    +$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1));
    +
    + This will return the $stuff only on the third + call and only if two parameters were set the second of + which must be the integer 1. + That should cover most simple prototyping situations. +

    +

    + A final tricky case is one object creating another, known + as a factory pattern. + Suppose that on a successful query to our imaginary + database, a result set is returned as an iterator with + each call to next() giving + one row until false. + This sounds like a simulation nightmare, but in fact it can all + be mocked using the mechanics above. +

    +

    + Here's how... +

    +Mock::generate('DatabaseConnection');
    +Mock::generate('ResultIterator');
    +
    +class DatabaseTest extends UnitTestCase {
    +    
    +    function testUserFinder() {
    +        $result = &new MockResultIterator();
    +        $result->setReturnValue('next', false);
    +        $result->setReturnValueAt(0, 'next', array(1, 'tom'));
    +        $result->setReturnValueAt(1, 'next', array(3, 'dick'));
    +        $result->setReturnValueAt(2, 'next', array(6, 'harry'));
    +        
    +        $connection = &new MockDatabaseConnection();
    +        $connection->setReturnValue('query', false);
    +        $connection->setReturnReference(
    +                'query',
    +                $result,
    +                array('select id, name from users'));
    +                
    +        $finder = &new UserFinder($connection);
    +        $this->assertIdentical(
    +                $finder->findNames(),
    +                array('tom', 'dick', 'harry'));
    +    }
    +}
    +
    + Now only if our + $connection is called with the correct + query() will the + $result be returned that is + itself exhausted after the third call to next(). + This should be enough + information for our UserFinder class, + the class actually + being tested here, to come up with goods. + A very precise test and not a real database in sight.

    @@ -388,18 +504,16 @@ Mock::generate('Log'); class LoggingSessionPoolTest extends UnitTestCase { ... function testFindSessionLogging() { - $session = &new MockSession($this); - $pool = &new MockSessionPool($this); + $session = &new MockSession(); + $pool = &new MockSessionPool(); $pool->setReturnReference('findSession', $session); $pool->expectOnce('findSession', array('abc')); - $log = &new MockLog($this); + $log = &new MockLog(); $log->expectOnce('message', array('Starting session abc')); $logging_pool = &new LoggingSessionPool($pool, $log); - $this->assertReference($logging_pool->findSession('abc'), $session); - $pool->tally(); - $log->tally(); + $this->assertReference($logging_pool->findSession('abc'), $session); } }

    @@ -426,15 +540,6 @@ class LoggingSessionPoolTest extends UnitTestCase { You can have wildcards and sequences and the order of evaluation is the same.

    -

    - If the call is never made then neither a pass nor a failure will - generated. - To get around this we must tell the mock when the test is over - so that the object can decide if the expectation has been met. - The unit tester assertion for this is triggered by the - tally() call at the end of - the test. -

    We use the same pattern to set up the mock logger. We tell it that it should have @@ -448,11 +553,6 @@ class LoggingSessionPoolTest extends UnitTestCase { LoggingSessionPool and feed it our preset mock objects. Everything is now under our control. - Finally we confirm that the - $session we gave our decorator - is the one that we get back and tell the mocks to run their - internal call count tests with the - tally() calls.

    This is still quite a bit of test code, but the code is very @@ -477,11 +577,11 @@ class LoggingSessionPoolTest extends UnitTestCase { - expectArguments($method, $args) + expect($method, $args) No - expectArgumentsAt($timing, $method, $args) + expectAt($timing, $method, $args) No @@ -589,8 +689,8 @@ class LoggingSessionPoolTest extends UnitTestCase { Mock::generate('Connection', 'BasicMockConnection'); class MockConnection extends BasicMockConnection { - function MockConnection(&$test, $wildcard = '*') { - $this->BasicMockConnection($test, $wildcard); + function MockConnection() { + $this->BasicMockConnection(); $this->setReturn('query', false); } } @@ -619,92 +719,6 @@ class LoggingSessionPoolTest extends UnitTestCase { stages of testing.

    -

    - -

    I think SimpleTest stinks!

    - -

    -

    - But at the time of writing it is the only one with mock objects, - so are you stuck with it? -

    -

    - No, not at all. - SimpleTest is a toolkit and one of those - tools is the mock objects which can be employed independently. - Suppose you have your own favourite unit tester and all your current - test cases are written using it. - Pretend that you have called your unit tester PHPUnit (everyone else has) - and the core test class looks like this... -

    -class PHPUnit {
    -    function PHPUnit() {
    -    }
    -    
    -    function assertion($message, $assertion) {
    -    }
    -    ...
    -}
    -
    - All the assertion() method does - is print some fancy output and the boolean assertion parameter determines - whether to print a pass or a failure. - Let's say that it is used like this... -
    -$unit_test = new PHPUnit();
    -$unit_test>assertion('I hope this file exists', file_exists('my_file'));
    -
    - How do you use mocks with this? -

    -

    - There is a protected method on the base mock class - SimpleMock called - _assertTrue() and - by overriding this method we can use our own assertion format. - We start with a subclass, in say my_mock.php... -

    -<?php
    -    require_once('simpletest/mock_objects.php');
    -    
    -    class MyMock extends SimpleMock() {
    -        function MyMock(&$test, $wildcard) {
    -            $this->SimpleMock($test, $wildcard);
    -        }
    -        
    -        function _assertTrue($assertion, $message) {
    -            $test = &$this->getTest();
    -            $test->assertion($message, $assertion);
    -        }
    -    }
    -?>
    -
    - Now instantiating MyMock will create - an object that speaks the same language as your tester. - The catch is of course that we never create such an object, the - code generator does. - We need just one more line of code to tell the generator to use - your mock instead... -
    -<?php
    -    require_once('simpletst/mock_objects.php');
    -    
    -    class MyMock extends SimpleMock() {
    -        function MyMock($test, $wildcard) {
    -            $this->SimpleMock(&$test, $wildcard);
    -        }
    -        
    -        function _assertTrue($assertion, $message , &$test) {
    -            $test->assertion($message, $assertion);
    -        }
    -    }
    -    SimpleTestOptions::setMockBaseClass('MyMock');
    -?>
    -
    - From now on you just include my_mock.php instead of the - default mock_objects.php version and you can introduce - mock objects into your existing test suite. -

    -
  • \n"; print "\n\n"; } - + /** * Paints the test failure with a breadcrumbs * trail of the nesting test suites below the @@ -113,22 +113,22 @@ print implode(" -> ", $breadcrumb); print " -> " . $this->_htmlEntities($message) . "
    \n"; } - + /** * Paints a PHP error or exception. * @param string $message Message is ignored. * @access public * @abstract */ - function paintException($message) { - parent::paintException($message); + function paintError($message) { + parent::paintError($message); print "Exception: "; $breadcrumb = $this->getTestList(); array_shift($breadcrumb); print implode(" -> ", $breadcrumb); print " -> " . $this->_htmlEntities($message) . "
    \n"; } - + /** * Paints formatted text such as dumped variables. * @param string $message Text to show. @@ -137,7 +137,7 @@ function paintFormattedMessage($message) { print '
    ' . $this->_htmlEntities($message) . '
    '; } - + /** * Character set adjusted entity conversion. * @param string $message Plain text or Unicode message. @@ -148,7 +148,7 @@ return htmlentities($message, ENT_COMPAT, $this->_character_set); } } - + /** * Sample minimal test displayer. Generates only * failure messages and a pass count. For command @@ -159,7 +159,7 @@ * @subpackage UnitTester */ class TextReporter extends SimpleReporter { - + /** * Does nothing yet. The first output will * be sent on the first test start. @@ -168,7 +168,7 @@ function TextReporter() { $this->SimpleReporter(); } - + /** * Paints the title only. * @param string $test_name Name class of test. @@ -181,7 +181,7 @@ print "$test_name\n"; flush(); } - + /** * Paints the end of the test with a summary of * the passes and failures. @@ -199,13 +199,12 @@ ", 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. + * @param string $message Failure message displayed in + * the context of the other tests. * @access public */ function paintFail($message) { @@ -216,18 +215,18 @@ 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 paintException($message) { - parent::paintException($message); + 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. @@ -238,4 +237,131 @@ 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 { + protected $_just_this_case =false; + protected $_just_this_test = false; + protected $_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; + } + } + } +?> \ No newline at end of file diff --git a/tests/test_tools/simpletest/scorer.php b/tests/test_tools/simpletest/scorer.php index 6c321f8c..e9cff6a6 100644 --- a/tests/test_tools/simpletest/scorer.php +++ b/tests/test_tools/simpletest/scorer.php @@ -3,9 +3,13 @@ * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester - * @version $Id: scorer.php,v 1.4 2005/01/11 04:03:46 lastcraft Exp $ + * @version $Id: scorer.php,v 1.12 2006/02/06 06:05:18 lastcraft Exp $ */ - + + /**#@+*/ + require_once(dirname(__FILE__) . '/invoker.php'); + /**#@-*/ + /** * Can recieve test events and display them. Display * is achieved by making display methods available @@ -19,7 +23,7 @@ protected $_fails; protected $_exceptions; protected $_is_dry_run; - + /** * Starts the test run with no results. * @access public @@ -30,16 +34,18 @@ $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. @@ -50,6 +56,17 @@ 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. @@ -63,23 +80,24 @@ } return true; } - + /** - * Paints the start of a test method. + * 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 paintMethodStart($test_name) { + function paintGroupStart($test_name, $size) { } - + /** - * Paints the end of a test method. + * Paints the end of a group test. * @param string $test_name Name of test or other label. * @access public */ - function paintMethodEnd($test_name) { + function paintGroupEnd($test_name) { } - + /** * Paints the start of a test case. * @param string $test_name Name of test or other label. @@ -87,7 +105,7 @@ */ function paintCaseStart($test_name) { } - + /** * Paints the end of a test case. * @param string $test_name Name of test or other label. @@ -95,24 +113,23 @@ */ function paintCaseEnd($test_name) { } - + /** - * Paints the start of a group test. + * Paints the start of a test method. * @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) { + function paintMethodStart($test_name) { } - + /** - * Paints the end of a group test. + * Paints the end of a test method. * @param string $test_name Name of test or other label. * @access public */ - function paintGroupEnd($test_name) { + function paintMethodEnd($test_name) { } - + /** * Increments the pass count. * @param string $message Message is ignored. @@ -121,7 +138,7 @@ function paintPass($message) { $this->_passes++; } - + /** * Increments the fail count. * @param string $message Message is ignored. @@ -130,27 +147,18 @@ function paintFail($message) { $this->_fails++; } - + /** - * Deals with PHP 4 throwing an error. + * 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->paintException($message); - } - - /** - * Deals with PHP 5 throwing an exception - * This isn't really implemented yet. - * @param Exception $exception Object thrown. - * @access public - */ - function paintException($exception) { $this->_exceptions++; } - + /** * Accessor for the number of passes so far. * @return integer Number of passes. @@ -159,7 +167,7 @@ function getPassCount() { return $this->_passes; } - + /** * Accessor for the number of fails so far. * @return integer Number of fails. @@ -168,7 +176,7 @@ function getFailCount() { return $this->_fails; } - + /** * Accessor for the number of untrapped errors * so far. @@ -178,7 +186,7 @@ function getExceptionCount() { return $this->_exceptions; } - + /** * Paints a simple supplementary message. * @param string $message Text to display. @@ -186,7 +194,7 @@ */ function paintMessage($message) { } - + /** * Paints a formatted ASCII message such as a * variable dump. @@ -195,7 +203,7 @@ */ function paintFormattedMessage($message) { } - + /** * By default just ignores user generated events. * @param string $type Event type as text. @@ -218,7 +226,7 @@ protected $_test_stack; protected $_size; protected $_progress; - + /** * Starts the display with no results in. * @access public @@ -229,7 +237,7 @@ $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 @@ -248,7 +256,7 @@ } $this->_test_stack[] = $test_name; } - + /** * Paints the end of a group test. Will paint the page * footer if the stack of tests has unwound. @@ -262,7 +270,7 @@ $this->paintFooter($test_name); } } - + /** * Paints the start of a test case. Will also paint * the page header and footer if this is the @@ -280,7 +288,7 @@ } $this->_test_stack[] = $test_name; } - + /** * Paints the end of a test case. Will paint the page * footer if the stack of tests has unwound. @@ -294,7 +302,7 @@ $this->paintFooter($test_name); } } - + /** * Paints the start of a test method. * @param string $test_name Name of test that is starting. @@ -303,7 +311,7 @@ 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. @@ -313,7 +321,7 @@ function paintMethodEnd($test_name) { array_pop($this->_test_stack); } - + /** * Paints the test document header. * @param string $test_name First test top level @@ -323,7 +331,7 @@ */ function paintHeader($test_name) { } - + /** * Paints the test document footer. * @param string $test_name The top level test. @@ -332,7 +340,7 @@ */ function paintFooter($test_name) { } - + /** * Accessor for internal test stack. For * subclasses that need to see the whole test @@ -343,7 +351,7 @@ function getTestList() { return $this->_test_stack; } - + /** * Accessor for total test size in number * of test cases. Null until the first @@ -354,7 +362,7 @@ function getTestCaseCount() { return $this->_size; } - + /** * Accessor for the number of test cases * completed so far. @@ -364,15 +372,406 @@ function getTestCaseProgress() { return $this->_progress; } - + /** * Static check for running in the comand line. * @return boolean True if CLI. * @access public * @static */ - static function inCli() { + function inCli() { return php_sapi_name() == 'cli'; } } + + /** + * For modifying the behaviour of the visual reporters. + * @package SimpleTest + * @subpackage UnitTester + */ + class SimpleReporterDecorator { + protected $_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 { + protected $_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); + } + } + } ?> \ No newline at end of file diff --git a/tests/test_tools/simpletest/selector.php b/tests/test_tools/simpletest/selector.php new file mode 100644 index 00000000..78ee0fef --- /dev/null +++ b/tests/test_tools/simpletest/selector.php @@ -0,0 +1,133 @@ +_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 { + protected $_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 { + protected $_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 { + protected $_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); + } + } +?> \ No newline at end of file diff --git a/tests/test_tools/simpletest/shell_tester.php b/tests/test_tools/simpletest/shell_tester.php index 7bab8ce0..572d4121 100644 --- a/tests/test_tools/simpletest/shell_tester.php +++ b/tests/test_tools/simpletest/shell_tester.php @@ -3,13 +3,13 @@ * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester - * @version $Id: shell_tester.php,v 1.14 2004/08/17 18:18:32 lastcraft Exp $ + * @version $Id: shell_tester.php,v 1.19 2005/08/03 17:26:55 lastcraft Exp $ */ /**#@+ * include other SimpleTest class files */ - require_once(dirname(__FILE__) . '/simple_test.php'); + require_once(dirname(__FILE__) . '/test_case.php'); /**#@-*/ /** @@ -127,6 +127,40 @@ $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. @@ -153,7 +187,7 @@ */ function assertOutput($expected, $message = "%s") { $shell = $this->_getShell(); - return $this->assertExpectation( + return $this->assert( new EqualExpectation($expected), $shell->getOutput(), $message); @@ -169,8 +203,8 @@ */ function assertOutputPattern($pattern, $message = "%s") { $shell = $this->_getShell(); - return $this->assertExpectation( - new WantedPatternExpectation($pattern), + return $this->assert( + new PatternExpectation($pattern), $shell->getOutput(), $message); } @@ -185,8 +219,8 @@ */ function assertNoOutputPattern($pattern, $message = "%s") { $shell = $this->_getShell(); - return $this->assertExpectation( - new UnwantedPatternExpectation($pattern), + return $this->assert( + new NoPatternExpectation($pattern), $shell->getOutput(), $message); } @@ -226,8 +260,8 @@ */ function assertFilePattern($pattern, $path, $message = "%s") { $shell = $this->_getShell(); - return $this->assertExpectation( - new WantedPatternExpectation($pattern), + return $this->assert( + new PatternExpectation($pattern), implode('', file($path)), $message); } @@ -243,8 +277,8 @@ */ function assertNoFilePattern($pattern, $path, $message = "%s") { $shell = $this->_getShell(); - return $this->assertExpectation( - new UnwantedPatternExpectation($pattern), + return $this->assert( + new NoPatternExpectation($pattern), implode('', file($path)), $message); } @@ -255,7 +289,7 @@ * @return Shell Current shell. * @access protected */ - function _getShell() { + function &_getShell() { return $this->_current_shell; } @@ -264,8 +298,9 @@ * @return Shell New shell object. * @access protected */ - function _createShell() { - return new SimpleShell(); + function &_createShell() { + $shell = new SimpleShell(); + return $shell; } } ?> \ No newline at end of file diff --git a/tests/test_tools/simpletest/simpletest.php b/tests/test_tools/simpletest/simpletest.php new file mode 100644 index 00000000..ca0d4e86 --- /dev/null +++ b/tests/test_tools/simpletest/simpletest.php @@ -0,0 +1,282 @@ += 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(); + } + } +?> \ No newline at end of file diff --git a/tests/test_tools/simpletest/socket.php b/tests/test_tools/simpletest/socket.php index 4c3c592d..b627a169 100644 --- a/tests/test_tools/simpletest/socket.php +++ b/tests/test_tools/simpletest/socket.php @@ -3,13 +3,13 @@ * base include file for SimpleTest * @package SimpleTest * @subpackage MockObjects - * @version $Id: socket.php,v 1.23 2004/09/30 16:46:31 lastcraft Exp $ + * @version $Id: socket.php,v 1.26 2005/08/29 00:57:48 lastcraft Exp $ */ /**#@+ * include SimpleTest files */ - require_once(dirname(__FILE__) . '/options.php'); + require_once(dirname(__FILE__) . '/compatibility.php'); /**#@-*/ /** @@ -73,25 +73,26 @@ */ class SimpleSocket extends SimpleStickyError { protected $_handle; - protected $_is_open; - protected $_sent; + protected $_is_open = false; + protected $_sent = ''; + public $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 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) { + function SimpleSocket($host, $port, $timeout, $block_size = 255) { $this->SimpleStickyError(); - $this->_is_open = false; - $this->_sent = ''; 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); } @@ -122,16 +123,15 @@ * Reads data from the socket. The error suppresion * is a workaround for PHP4 always throwing a warning * with a secure socket. - * @param integer $block_size Size of chunk to read. - * @return integer Incoming bytes. False + * @return integer/boolean Incoming bytes. False * on error. * @access public */ - function read($block_size = 255) { + function read() { if ($this->isError() || ! $this->isOpen()) { return false; } - $raw = @fread($this->_handle, $block_size); + $raw = @fread($this->_handle, $this->_block_size); if ($raw === false) { $this->_setError('Cannot read from socket'); $this->close(); diff --git a/tests/test_tools/simpletest/tag.php b/tests/test_tools/simpletest/tag.php index 9386712a..060265d2 100644 --- a/tests/test_tools/simpletest/tag.php +++ b/tests/test_tools/simpletest/tag.php @@ -3,13 +3,14 @@ * Base include file for SimpleTest. * @package SimpleTest * @subpackage WebTester - * @version $Id: tag.php,v 1.73 2005/02/02 22:49:36 lastcraft Exp $ + * @version $Id: tag.php,v 1.95 2006/02/05 00:34:29 lastcraft Exp $ */ /**#@+ * include SimpleTest files */ require_once(dirname(__FILE__) . '/parser.php'); + require_once(dirname(__FILE__) . '/encoding.php'); /**#@-*/ /** @@ -31,7 +32,7 @@ * converted to lower case. */ function SimpleTag($name, $attributes) { - $this->_name = $name; + $this->_name = strtolower(trim($name)); $this->_attributes = $attributes; $this->_content = ''; } @@ -46,6 +47,19 @@ 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. @@ -73,7 +87,7 @@ } /** - * List oflegal child elements. + * List of legal child elements. * @return array List of element names. * @access public */ @@ -92,9 +106,6 @@ if (! isset($this->_attributes[$label])) { return false; } - if ($this->_attributes[$label] === '') { - return true; - } return (string)$this->_attributes[$label]; } @@ -125,7 +136,7 @@ * @access public */ function getText() { - return SimpleSaxParser::normalise($this->_content); + return SimpleHtmlSaxParser::normalise($this->_content); } /** @@ -193,6 +204,7 @@ */ class SimpleWidget extends SimpleTag { protected $_value; + protected $_label; protected $_is_set; /** @@ -204,6 +216,7 @@ function SimpleWidget($name, $attributes) { $this->SimpleTag($name, $attributes); $this->_value = false; + $this->_label = false; $this->_is_set = false; } @@ -223,14 +236,7 @@ * @access public */ function getDefault() { - $default = $this->getAttribute('value'); - if ($default === true) { - $default = ''; - } - if ($default === false) { - $default = ''; - } - return $default; + return $this->getAttribute('value'); } /** @@ -267,6 +273,37 @@ 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()); + } + } } /** @@ -326,9 +363,6 @@ */ function SimpleSubmitTag($attributes) { $this->SimpleWidget('input', $attributes); - if ($this->getAttribute('name') === false) { - $this->_setAttribute('name', 'submit'); - } if ($this->getAttribute('value') === false) { $this->_setAttribute('value', 'Submit'); } @@ -363,12 +397,13 @@ } /** - * Gets the values submitted as a form. - * @return array Hash of name and values. + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. * @access public */ - function getSubmitValues() { - return array($this->getName() => $this->getValue()); + function isLabel($label) { + return trim($label) == trim($this->getLabel()); } } @@ -420,14 +455,30 @@ } /** - * Gets the values submitted as a form. - * @return array Hash of name and values. + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. * @access public */ - function getSubmitValues($x, $y) { - return array( - $this->getName() . '.x' => $x, - $this->getName() . '.y' => $y); + 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); + } } } @@ -478,19 +529,13 @@ } /** - * Gets the values submitted as a form. Gone - * for the Mozilla defaults values. - * @return array Hash of name and values. + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. * @access public */ - function getSubmitValues() { - if ($this->getAttribute('name') === false) { - return array(); - } - if ($this->getAttribute('value') === false) { - return array($this->getName() => ''); - } - return array($this->getName() => $this->getValue()); + function isLabel($label) { + return trim($label) == trim($this->getLabel()); } } @@ -516,13 +561,7 @@ * @access public */ function getDefault() { - if ($this->_wrapIsEnabled()) { - return wordwrap( - $this->getContent(), - (integer)$this->getAttribute('cols'), - "\n"); - } - return $this->getContent(); + return $this->_wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent())); } /** @@ -532,13 +571,7 @@ * @access public */ function setValue($value) { - if ($this->_wrapIsEnabled()) { - $value = wordwrap( - $value, - (integer)$this->getAttribute('cols'), - "\n"); - } - return parent::setValue($value); + return parent::setValue($this->_wrap($value)); } /** @@ -555,25 +588,56 @@ } 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; + } } /** - * Checkbox widget. + * File upload widget. * @package SimpleTest * @subpackage WebTester */ - class SimpleCheckboxTag extends SimpleWidget { + class SimpleUploadTag extends SimpleWidget { /** * Starts with attributes only. * @param hash $attributes Attribute names and * string values. */ - function SimpleCheckboxTag($attributes) { + function SimpleUploadTag($attributes) { $this->SimpleWidget('input', $attributes); - if ($this->getAttribute('value') === false) { - $this->_setAttribute('value', 'on'); - } } /** @@ -586,34 +650,18 @@ } /** - * The only allowed value in the one in the - * "value" attribute. The default for this - * attribute is "on". - * @param string $value New value. - * @return boolean True if allowed. + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. * @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. The default - * value is "on". - * @return string Parsed value. - * @access public - */ - function getDefault() { - if ($this->getAttribute('checked')) { - return $this->getAttribute('value'); + function write($encoding) { + if (! file_exists($this->getValue())) { + return; } - return false; + $encoding->attach( + $this->getName(), + implode('', file($this->getValue())), + basename($this->getValue())); } } @@ -664,7 +712,7 @@ */ function getDefault() { for ($i = 0, $count = count($this->_options); $i < $count; $i++) { - if ($this->_options[$i]->getAttribute('selected')) { + if ($this->_options[$i]->getAttribute('selected') !== false) { return $this->_options[$i]->getDefault(); } } @@ -682,7 +730,7 @@ */ function setValue($value) { for ($i = 0, $count = count($this->_options); $i < $count; $i++) { - if (trim($this->_options[$i]->getContent()) == trim($value)) { + if ($this->_options[$i]->isValue($value)) { $this->_choice = $i; return true; } @@ -752,7 +800,7 @@ function getDefault() { $default = array(); for ($i = 0, $count = count($this->_options); $i < $count; $i++) { - if ($this->_options[$i]->getAttribute('selected')) { + if ($this->_options[$i]->getAttribute('selected') !== false) { $default[] = $this->_options[$i]->getDefault(); } } @@ -760,25 +808,29 @@ } /** - * Can only set allowed values. - * @param array $values New choices. - * @return boolean True if allowed. + * 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($values) { - foreach ($values as $value) { - $is_option = false; + function setValue($desired) { + $achieved = array(); + foreach ($desired as $value) { + $success = false; for ($i = 0, $count = count($this->_options); $i < $count; $i++) { - if (trim($this->_options[$i]->getContent()) == trim($value)) { - $is_option = true; + if ($this->_options[$i]->isValue($value)) { + $achieved[] = $this->_options[$i]->getValue(); + $success = true; break; } } - if (! $is_option) { + if (! $success) { return false; } } - $this->_values = $values; + $this->_values = $achieved; return true; } @@ -819,6 +871,20 @@ 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. @@ -831,6 +897,15 @@ } return $this->getAttribute('value'); } + + /** + * The content of options is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } } /** @@ -861,7 +936,7 @@ } /** - * The only allowed value in the one in the + * The only allowed value sn the one in the * "value" attribute. * @param string $value New value. * @return boolean True if allowed. @@ -871,7 +946,7 @@ if ($value === false) { return parent::setValue($value); } - if ($value != $this->getAttribute('value')) { + if ($value !== $this->getAttribute('value')) { return false; } return parent::setValue($value); @@ -883,27 +958,101 @@ * @access public */ function getDefault() { - if ($this->getAttribute('checked')) { + if ($this->getAttribute('checked') !== false) { return $this->getAttribute('value'); } return false; } } - + /** - * A group of tags with the same name within a form. + * Checkbox widget. * @package SimpleTest * @subpackage WebTester */ - class SimpleCheckboxGroup { - protected $_widgets; + class SimpleCheckboxTag extends SimpleWidget { /** - * Starts empty. + * 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 SimpleCheckboxGroup() { - $this->_widgets = array(); + 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 { + protected $_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; } /** @@ -917,7 +1066,19 @@ } /** - * Scans the checkboxes for one with the appropriate + * 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. @@ -931,27 +1092,39 @@ } return false; } - + /** - * Adds a tag to the group. - * @param SimpleWidget $widget + * 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 addWidget($widget) { - $this->_widgets[] = $widget; + function isLabel($label) { + for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { + if ($this->_widgets[$i]->isLabel($label)) { + return true; + } + } + return false; } /** - * Fetches the name for the widget from the first - * member. - * @return string Name of widget. + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. * @access public */ - function getName() { - if (count($this->_widgets) > 0) { - return $this->_widgets[0]->getName(); - } + 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 @@ -961,9 +1134,10 @@ */ function getValue() { $values = array(); - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - if ($this->_widgets[$i]->getValue()) { - $values[] = $this->_widgets[$i]->getValue(); + $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); @@ -976,9 +1150,10 @@ */ function getDefault() { $values = array(); - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - if ($this->_widgets[$i]->getDefault()) { - $values[] = $this->_widgets[$i]->getDefault(); + $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); @@ -996,12 +1171,13 @@ if (! $this->_valuesArePossible($values)) { return false; } - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - $possible = $this->_widgets[$i]->getAttribute('value'); - if (in_array($this->_widgets[$i]->getAttribute('value'), $values)) { - $this->_widgets[$i]->setValue($possible); + $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 { - $this->_widgets[$i]->setValue(false); + $widgets[$i]->setValue(false); } } return true; @@ -1017,8 +1193,9 @@ */ function _valuesArePossible($values) { $matches = array(); - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - $possible = $this->_widgets[$i]->getAttribute('value'); + $widgets = $this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); if (in_array($possible, $values)) { $matches[] = $possible; } @@ -1070,63 +1247,7 @@ * @package SimpleTest * @subpackage WebTester */ - class SimpleRadioGroup { - protected $_widgets; - - /** - * Starts empty. - * @access public - */ - function SimpleRadioGroup() { - $this->_widgets = array(); - } - - /** - * Accessor for an attribute. - * @param string $label Attribute name. - * @return boolean Always false. - * @access public - */ - function getAttribute($label) { - return false; - } - - /** - * Scans the checkboxes 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; - } - - /** - * Adds a tag to the group. - * @param SimpleWidget $widget - * @access public - */ - function addWidget($widget) { - $this->_widgets[] = $widget; - } - - /** - * 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(); - } - } + class SimpleRadioGroup extends SimpleTagGroup { /** * Each tag is tried in turn until one is @@ -1141,9 +1262,10 @@ return false; } $index = false; - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - if (! $this->_widgets[$i]->setValue($value)) { - $this->_widgets[$i]->setValue(false); + $widgets = $this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if (! $widgets[$i]->setValue($value)) { + $widgets[$i]->setValue(false); } } return true; @@ -1156,8 +1278,9 @@ * @access private */ function _valueIsPossible($value) { - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - if ($this->_widgets[$i]->getAttribute('value') == $value) { + $widgets = $this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getAttribute('value') == $value) { return true; } } @@ -1172,9 +1295,10 @@ * @access public */ function getValue() { - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - if ($this->_widgets[$i]->getValue()) { - return $this->_widgets[$i]->getValue(); + $widgets = $this->_getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + return $widgets[$i]->getValue(); } } return false; @@ -1187,15 +1311,42 @@ * @access public */ function getDefault() { - for ($i = 0, $count = count($this->_widgets); $i < $count; $i++) { - if ($this->_widgets[$i]->getDefault()) { - return $this->_widgets[$i]->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 diff --git a/tests/test_tools/simpletest/test_case.php b/tests/test_tools/simpletest/test_case.php new file mode 100644 index 00000000..1bf49789 --- /dev/null +++ b/tests/test_tools/simpletest/test_case.php @@ -0,0 +1,684 @@ += 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 { + protected $_label = false; + protected $_reporter; + protected $_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 { + protected $_label; + protected $_test_cases; + protected $_old_track_errors; + protected $_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 { + protected $_label; + protected $_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; + } + } +?> diff --git a/tests/test_tools/simpletest/unit_tester.php b/tests/test_tools/simpletest/unit_tester.php index fa5e28b4..88e10b14 100644 --- a/tests/test_tools/simpletest/unit_tester.php +++ b/tests/test_tools/simpletest/unit_tester.php @@ -3,17 +3,16 @@ * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester - * @version $Id: unit_tester.php,v 1.24 2005/01/13 01:31:53 lastcraft Exp $ + * @version $Id: unit_tester.php,v 1.31 2006/02/06 06:05:18 lastcraft Exp $ */ /**#@+ * include other SimpleTest class files */ - require_once(dirname(__FILE__) . '/simple_test.php'); - require_once(dirname(__FILE__) . '/errors.php'); + 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 @@ -22,7 +21,7 @@ * @subpackage UnitTester */ class UnitTestCase extends SimpleTestCase { - + /** * Creates an empty test case. Should be subclassed * with test methods for a functional test case. @@ -36,7 +35,7 @@ } $this->SimpleTestCase($label); } - + /** * Will be true if the value is null. * @param null $value Supposedly null value. @@ -51,7 +50,7 @@ "[" . $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. @@ -66,7 +65,7 @@ "[" . $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 @@ -78,12 +77,12 @@ * @access public */ function assertIsA($object, $type, $message = "%s") { - return $this->assertExpectation( + 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 @@ -95,12 +94,12 @@ * @access public */ function assertNotA($object, $type, $message = "%s") { - return $this->assertExpectation( + return $this->assert( new NotAExpectation($type), $object, $message); } - + /** * Will trigger a pass if the two parameters have * the same value only. Otherwise a fail. @@ -111,12 +110,12 @@ * @access public */ function assertEqual($first, $second, $message = "%s") { - return $this->assertExpectation( + return $this->assert( new EqualExpectation($first), $second, $message); } - + /** * Will trigger a pass if the two parameters have * a different value. Otherwise a fail. @@ -127,12 +126,46 @@ * @access public */ function assertNotEqual($first, $second, $message = "%s") { - return $this->assertExpectation( + 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. @@ -143,12 +176,12 @@ * @access public */ function assertIdentical($first, $second, $message = "%s") { - return $this->assertExpectation( + return $this->assert( new IdenticalExpectation($first), $second, $message); } - + /** * Will trigger a pass if the two parameters have * the different value or different type. @@ -159,12 +192,12 @@ * @access public */ function assertNotIdentical($first, $second, $message = "%s") { - return $this->assertExpectation( + return $this->assert( new NotIdenticalExpectation($first), $second, $message); } - + /** * Will trigger a pass if both parameters refer * to the same object. Fail otherwise. @@ -185,16 +218,34 @@ SimpleTestCompatibility::isReference($first, $second), $message); } - + /** * Will trigger a pass if both parameters refer - * to different objects. Fail otherwise. + * 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( @@ -206,7 +257,7 @@ SimpleTestCompatibility::isReference($first, $second), $message); } - + /** * Will trigger a pass if the Perl regex pattern * is found in the subject. Fail otherwise. @@ -217,13 +268,20 @@ * @return boolean True on pass * @access public */ - function assertWantedPattern($pattern, $subject, $message = "%s") { - return $this->assertExpectation( - new WantedPatternExpectation($pattern), + 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. @@ -234,13 +292,20 @@ * @return boolean True on pass * @access public */ - function assertNoUnwantedPattern($pattern, $subject, $message = "%s") { - return $this->assertExpectation( - new UnwantedPatternExpectation($pattern), + 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. @@ -249,12 +314,12 @@ * @access public */ function assertNoErrors($message = "%s") { - $queue =SimpleErrorQueue::instance(); + $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. @@ -265,39 +330,44 @@ * @access public */ function assertError($expected = false, $message = "%s") { - $queue =SimpleErrorQueue::instance(); + $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); - return $this->assertTrue( - ! $expected || ($expected == $content), - "Expected [$expected] in PHP error [$content] severity [$severity] in [$file] line [$line]"); + 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"); } - + /** - * Confirms that an error has occoured and - * that the error text matches a Perl regular - * expression. - * @param string $pattern Perl regular expresion to - * match against. - * @param string $message Message to display. - * @return boolean True on pass - * @access public + * 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 assertErrorPattern($pattern, $message = "%s") { - $queue =SimpleErrorQueue::instance(); - if ($queue->isEmpty()) { - $this->fail(sprintf($message, "Expected error not found")); - return; + function _coerceToExpectation($expected) { + if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) { + return $expected; } - list($severity, $content, $file, $line, $globals) = $queue->extract(); - $severity = SimpleErrorQueue::getSeverityAsString($severity); - return $this->assertTrue( - (boolean)preg_match($pattern, $content), - "Expected pattern match [$pattern] in PHP error [$content] severity [$severity] in [$file] line [$line]"); + return new EqualExpectation($expected); + } + + /** + * @deprecated + */ + function assertErrorPattern($pattern, $message = "%s") { + return $this->assertError(new PatternExpectation($pattern), $message); } } ?> diff --git a/tests/test_tools/simpletest/url.php b/tests/test_tools/simpletest/url.php index 03902ed0..5bfceb14 100644 --- a/tests/test_tools/simpletest/url.php +++ b/tests/test_tools/simpletest/url.php @@ -3,7 +3,7 @@ * base include file for SimpleTest * @package SimpleTest * @subpackage WebTester - * @version $Id: url.php,v 1.22 2005/02/02 23:25:23 lastcraft Exp $ + * @version $Id: url.php,v 1.29 2006/01/14 02:45:34 lastcraft Exp $ */ /**#@+ @@ -17,7 +17,8 @@ * 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. + * the path and tries to keep a raw, possibly unparsable, + * request string as long as possible. * @package SimpleTest * @subpackage WebTester */ @@ -30,7 +31,10 @@ protected $_path; protected $_request; protected $_fragment; + protected $_x; + protected $_y; protected $_target; + protected $_raw = false; /** * Constructor. Parses URL into sections. @@ -39,6 +43,7 @@ */ 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); @@ -49,7 +54,6 @@ } $this->_path = $this->_chompPath($url); $this->_request = $this->_parseRequest($this->_chompRequest($url)); - $this->_request->setCoordinates($x, $y); $this->_fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false); $this->_target = false; } @@ -96,7 +100,7 @@ */ function _chompLogin($url) { $prefix = ''; - if (preg_match('/(\/\/)(.*)/', $url, $matches)) { + if (preg_match('/^(\/\/)(.*)/', $url, $matches)) { $prefix = $matches[1]; $url = $matches[2]; } @@ -123,7 +127,7 @@ * @access private */ function _chompHost($url) { - if (preg_match('/(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) { + if (preg_match('/^(\/\/)(.*?)(\/.*|\?.*|#.*|$)/', $url, $matches)) { $url = $matches[3]; return $matches[2]; } @@ -178,7 +182,8 @@ * @access private */ function _parseRequest($raw) { - $request = new SimpleFormEncoding(); + $this->_raw = $raw; + $request = new SimpleGetEncoding(); foreach (split("&", $raw) as $pair) { if (preg_match('/(.*?)=(.*)/', $pair, $matches)) { $request->add($matches[1], urldecode($matches[2])); @@ -292,13 +297,29 @@ 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->_request->getX(); + return $this->_x; } /** @@ -307,17 +328,23 @@ * @access public */ function getY() { - return $this->_request->getY(); + return $this->_y; } /** * Accessor for current request parameters - * in URL string form + * 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() { - $encoded = $this->_request->asString(); + if ($this->_raw) { + $encoded = $this->_raw; + } else { + $encoded = $this->_request->asUrlRequest(); + } if ($encoded) { return '?' . preg_replace('/^\?/', '', $encoded); } @@ -331,6 +358,7 @@ * @access public */ function addRequestParameter($key, $value) { + $this->_raw = false; $this->_request->add($key, $value); } @@ -341,6 +369,7 @@ * @access public */ function addRequestParameters($parameters) { + $this->_raw = false; $this->_request->merge($parameters); } @@ -349,18 +378,8 @@ * @access public */ function clearRequest() { - $this->_request = new SimpleFormEncoding(); - } - - /** - * Sets image coordinates. Set to flase to clear - * them. - * @param integer $x Horizontal position. - * @param integer $y Vertical position. - * @access public - */ - function setCoordinates($x = false, $y = false) { - $this->_request->setCoordinates($x, $y); + $this->_raw = false; + $this->_request = new SimpleGetEncoding(); } /** @@ -380,6 +399,7 @@ * @access public */ function setTarget($frame) { + $this->_raw = false; $this->_target = $frame; } @@ -402,7 +422,8 @@ } $encoded = $this->getEncodedRequest(); $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; - return "$scheme://$identity$host$path$encoded$fragment"; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return "$scheme://$identity$host$path$encoded$fragment$coords"; } /** @@ -417,27 +438,23 @@ $base = new SimpleUrl($base); } $scheme = $this->getScheme() ? $this->getScheme() : $base->getScheme(); - $host = $this->getHost() ? $this->getHost() : $base->getHost(); - $port = $this->_extractAbsolutePort($base); + 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)); - $identity = $this->_getIdentity() ? $this->_getIdentity() . '@' : ''; $encoded = $this->getEncodedRequest(); $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; - return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment"); - } - - /** - * Extracts the port from the base URL if it's needed, but - * not present, in the current URL. - * @param string/SimpleUrl $base Base URL. - * @param string Absolute port number. - * @access private - */ - function _extractAbsolutePort($base) { - if ($this->getHost()) { - return ($this->getPort() ? ':' . $this->getPort() : ''); - } - return ($base->getPort() ? ':' . $base->getPort() : ''); + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords"); } /** @@ -473,10 +490,10 @@ /** * Extracts the username and password for use in rendering * a URL. - * @return string/boolean Form of username:password@ or false. - * @access private + * @return string/boolean Form of username:password or false. + * @access public */ - function _getIdentity() { + function getIdentity() { if ($this->_username && $this->_password) { return $this->_username . ':' . $this->_password; } diff --git a/tests/test_tools/simpletest/user_agent.php b/tests/test_tools/simpletest/user_agent.php index c65d5110..7216cbd1 100644 --- a/tests/test_tools/simpletest/user_agent.php +++ b/tests/test_tools/simpletest/user_agent.php @@ -3,153 +3,24 @@ * Base include file for SimpleTest * @package SimpleTest * @subpackage WebTester - * @version $Id: user_agent.php,v 1.43 2005/01/02 22:46:08 lastcraft Exp $ + * @version $Id: user_agent.php,v 1.55 2005/12/07 18:04:58 lastcraft Exp $ */ /**#@+ * 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'); /**#@-*/ - define('DEFAULT_MAX_REDIRECTS', 3); - define('DEFAULT_CONNECTION_TIMEOUT', 15); + if (! defined('DEFAULT_MAX_REDIRECTS')) { + define('DEFAULT_MAX_REDIRECTS', 3); + } - /** - * Repository for cookies. This stuff is a - * tiny bit browser dependent. - * @package SimpleTest - * @subpackage WebTester - */ - class SimpleCookieJar { - protected $_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); - } - } - - /** - * Adds a cookie to the jar. This will overwrite - * cookies with matching host, paths and keys. - * @param SimpleCookie $cookie New cookie. - * @access public - */ - function setCookie($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) { - $this->_cookies[$i] = $cookie; - return; - } - } - $this->_cookies[] = $cookie; - } - - /** - * Fetches a hash of all valid cookies filtered - * by host, path and keyed by name - * Any cookies with missing categories will not - * be filtered out by that category. Expired - * cookies must be cleared by restarting the session. - * @param string $host Host name requirement. - * @param string $path Path encompassing cookies. - * @return hash Valid cookie objects keyed - * on the cookie name. - * @access public - */ - function getValidCookies($host = false, $path = "/") { - $valid_cookies = array(); - foreach ($this->_cookies as $cookie) { - if ($this->_isMatch($cookie, $host, $path, $cookie->getName())) { - $valid_cookies[] = $cookie; - } - } - return $valid_cookies; - } - - /** - * 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; - } - - /** - * Adds the current cookies to a request. - * @param SimpleHttpRequest $request Request to modify. - * @param SimpleUrl $url Cookie selector. - * @access private - */ - function addHeaders($request, $url) { - $cookies = $this->getValidCookies($url->getHost(), $url->getPath()); - foreach ($cookies as $cookie) { - $request->setCookie($cookie); - } - } + if (! defined('DEFAULT_CONNECTION_TIMEOUT')) { + define('DEFAULT_CONNECTION_TIMEOUT', 15); } /** @@ -160,13 +31,14 @@ */ class SimpleUserAgent { protected $_cookie_jar; + protected $_cookies_enabled = true; protected $_authenticator; - protected $_max_redirects; - protected $_proxy; - protected $_proxy_username; - protected $_proxy_password; - protected $_connection_timeout; - protected $_additional_headers; + protected $_max_redirects = DEFAULT_MAX_REDIRECTS; + protected $_proxy = false; + protected $_proxy_username = false; + protected $_proxy_password = false; + protected $_connection_timeout = DEFAULT_CONNECTION_TIMEOUT; + protected $_additional_headers = array(); /** * Starts with no cookies, realms or proxies. @@ -175,12 +47,6 @@ function SimpleUserAgent() { $this->_cookie_jar = new SimpleCookieJar(); $this->_authenticator = new SimpleAuthenticator(); - $this->setMaximumRedirects(DEFAULT_MAX_REDIRECTS); - $this->_proxy = false; - $this->_proxy_username = false; - $this->_proxy_password = false; - $this->setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT); - $this->_additional_headers = array(); } /** @@ -227,11 +93,7 @@ * @access public */ function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { - $cookie = new SimpleCookie($name, $value, $path, $expiry); - if ($host) { - $cookie->setHost($host); - } - $this->_cookie_jar->setCookie($cookie); + $this->_cookie_jar->setCookie($name, $value, $host, $path, $expiry); } /** @@ -245,23 +107,14 @@ * @access public */ function getCookieValue($host, $path, $name) { - $longest_path = ''; - foreach ($this->_cookie_jar->getValidCookies($host, $path) as $cookie) { - if ($name == $cookie->getName()) { - if (strlen($cookie->getPath()) > strlen($longest_path)) { - $value = $cookie->getValue(); - $longest_path = $cookie->getPath(); - } - } - } - return (isset($value) ? $value : false); + 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 Null if there is no base URL, false + * @return string/boolean Null if there is no base URL, false * if the cookie is not set. * @access public */ @@ -272,6 +125,22 @@ 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. @@ -305,7 +174,7 @@ $this->_proxy = false; return; } - if (strncmp($proxy, 'http://', 7) != 0) { + if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) { $proxy = 'http://'. $proxy; } $this->_proxy = new SimpleUrl($proxy); @@ -338,18 +207,17 @@ /** * Fetches a URL as a response object. Will keep trying if redirected. * It will also collect authentication realm information. - * @param string $method GET, POST, etc. - * @param string/SimpleUrl $url Target to fetch. - * @param SimpleFormEncoding $parameters Additional parameters for request. - * @return SimpleHttpResponse Hopefully the target page. + * @param string/SimpleUrl $url Target to fetch. + * @param SimpleEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. * @access public */ - function fetchResponse($method, $url, $parameters = false) { - if ($method != 'POST') { - $url->addRequestParameters($parameters); - $parameters = false; + function &fetchResponse($url, $encoding) { + if ($encoding->getMethod() != 'POST') { + $url->addRequestParameters($encoding); + $encoding->clear(); } - $response = $this->_fetchWhileRedirected($method, $url, $parameters); + $response = $this->_fetchWhileRedirected($url, $encoding); if ($headers = $response->getHeaders()) { if ($headers->isChallenge()) { $this->_authenticator->addRealm( @@ -364,84 +232,72 @@ /** * Fetches the page until no longer redirected or * until the redirect limit runs out. - * @param string $method GET, POST, etc. * @param SimpleUrl $url Target to fetch. - * @param SimpelFormEncoding $parameters Additional parameters for request. + * @param SimpelFormEncoding $encoding Additional parameters for request. * @return SimpleHttpResponse Hopefully the target page. * @access private */ - function _fetchWhileRedirected($method, $url, $parameters) { + function &_fetchWhileRedirected($url, $encoding) { $redirects = 0; do { - $response = $this->_fetch($method, $url, $parameters); + $response = $this->_fetch($url, $encoding); if ($response->isError()) { return $response; } $headers = $response->getHeaders(); $location = new SimpleUrl($headers->getLocation()); $url = $location->makeAbsolute($url); - $this->_addCookiesToJar($url, $headers->getNewCookies()); + if ($this->_cookies_enabled) { + $headers->writeCookiesToJar($this->_cookie_jar, $url); + } if (! $headers->isRedirect()) { break; } - $method = 'GET'; - $parameters = false; + $encoding = new SimpleGetEncoding(); } while (! $this->_isTooManyRedirects(++$redirects)); return $response; } /** * Actually make the web request. - * @param string $method GET, POST, etc. * @param SimpleUrl $url Target to fetch. - * @param SimpleFormEncoding $parameters Additional parameters for request. + * @param SimpleFormEncoding $encoding Additional parameters for request. * @return SimpleHttpResponse Headers and hopefully content. * @access protected */ - function _fetch($method, $url, $parameters) { - if (! $parameters) { - $parameters = new SimpleFormEncoding(); - } - $request = $this->_createRequest($method, $url, $parameters); - return $request->fetch($this->_connection_timeout); + function &_fetch($url, $encoding) { + $request = $this->_createRequest($url, $encoding); + $response = $request->fetch($this->_connection_timeout); + return $response; } /** * Creates a full page request. - * @param string $method Fetching method. - * @param SimpleUrl $url Target to fetch as url object. - * @param SimpleFormEncoding $parameters POST/GET parameters. - * @return SimpleHttpRequest New request. + * @param SimpleUrl $url Target to fetch as url object. + * @param SimpleFormEncoding $encoding POST/GET parameters. + * @return SimpleHttpRequest New request. * @access private */ - function _createRequest($method, $url, $parameters) { - $request = $this->_createHttpRequest($method, $url, $parameters); + function &_createRequest($url, $encoding) { + $request = $this->_createHttpRequest($url, $encoding); $this->_addAdditionalHeaders($request); - $this->_cookie_jar->addHeaders($request, $url); + if ($this->_cookies_enabled) { + $request->readCookiesFromJar($this->_cookie_jar, $url); + } $this->_authenticator->addHeaders($request, $url); return $request; } /** * Builds the appropriate HTTP request object. - * @param string $method Fetching method. * @param SimpleUrl $url Target to fetch as url object. * @param SimpleFormEncoding $parameters POST/GET parameters. * @return SimpleHttpRequest New request object. * @access protected */ - function _createHttpRequest($method, $url, $parameters) { - if ($method == 'POST') { - $request = new SimpleHttpRequest( - $this->_createRoute($url), - 'POST', - $parameters); - return $request; - } - if ($parameters) { - $url->addRequestParameters($parameters); - } - return new SimpleHttpRequest($this->_createRoute($url), $method); + function &_createHttpRequest($url, $encoding) { + $request = new SimpleHttpRequest($this->_createRoute($url), $encoding); + return $request; } /** @@ -450,15 +306,17 @@ * @return SimpleRoute Route to take to fetch URL. * @access protected */ - function _createRoute($url) { + function &_createRoute($url) { if ($this->_proxy) { - return new SimpleProxyRoute( + $route = new SimpleProxyRoute( $url, $this->_proxy, $this->_proxy_username, $this->_proxy_password); + } else { + $route = new SimpleRoute($url); } - return new SimpleRoute($url); + return $route; } /** @@ -471,20 +329,5 @@ $request->addHeaderLine($header); } } - - /** - * Extracts new cookies into the cookie jar. - * @param SimpleUrl $url Target to fetch as url object. - * @param array $cookies New cookies. - * @access private - */ - function _addCookiesToJar($url, $cookies) { - foreach ($cookies as $cookie) { - if ($url->getHost()) { - $cookie->setHost($url->getHost()); - } - $this->_cookie_jar->setCookie($cookie); - } - } } ?> \ No newline at end of file diff --git a/tests/test_tools/simpletest/web_tester.php b/tests/test_tools/simpletest/web_tester.php index 4981d25a..203f729b 100644 --- a/tests/test_tools/simpletest/web_tester.php +++ b/tests/test_tools/simpletest/web_tester.php @@ -3,13 +3,13 @@ * Base include file for SimpleTest. * @package SimpleTest * @subpackage WebTester - * @version $Id: web_tester.php,v 1.92 2005/02/22 02:39:22 lastcraft Exp $ + * @version $Id: web_tester.php,v 1.114 2006/02/05 02:04:24 lastcraft Exp $ */ /**#@+ * include other SimpleTest class files */ - require_once(dirname(__FILE__) . '/simple_test.php'); + 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'); @@ -25,11 +25,14 @@ /** * Sets the field value to compare against. - * @param mixed $value Test value to match. + * @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) { - $this->SimpleExpectation(); + function FieldExpectation($value, $message = '%s') { + $this->SimpleExpectation($message); if (is_array($value)) { sort($value); } @@ -135,16 +138,21 @@ /** * Sets the field and value to compare against. * @param string $header Case insenstive trimmed header name. - * @param string $value Optional value to compare. If not - * given then any value will match. - */ - function HttpHeaderExpectation($header, $value = false) { + * @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 subclases. + * Accessor for aggregated object. * @return mixed Expectation set in constructor. * @access protected */ @@ -198,7 +206,7 @@ * @access private */ function _testHeaderLine($line) { - if (count($parsed = split(':', $line)) < 2) { + if (count($parsed = split(':', $line, 2)) < 2) { return false; } list($header, $value) = $parsed; @@ -219,6 +227,9 @@ if ($expected === false) { return true; } + if (SimpleExpectation::isExpectation($expected)) { + return $expected->test(trim($value)); + } return (trim($value) == trim($expected)); } @@ -230,14 +241,16 @@ * @access public */ function testMessage($compare) { - $expectation = $this->_expected_header; - if ($this->_expected_value) { - $expectation .= ': ' . $this->_expected_header; + 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 [$expectation] found [$line]"; + return "Searching for header [$message] found [$line]"; } else { - return "Failed to find header [$expectation]"; + return "Failed to find header [$message]"; } } } @@ -248,16 +261,18 @@ * @package SimpleTest * @subpackage WebTester */ - class HttpUnwantedHeaderExpectation extends HttpHeaderExpectation { + class NoHttpHeaderExpectation extends HttpHeaderExpectation { protected $_expected_header; protected $_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 HttpUnwantedHeaderExpectation($unwanted) { - $this->HttpHeaderExpectation($unwanted); + function NoHttpHeaderExpectation($unwanted, $message = '%s') { + $this->HttpHeaderExpectation($unwanted, false, $message); } /** @@ -286,42 +301,13 @@ } } } - - /** - * Test for a specific HTTP header within a header block. - * @package SimpleTest - * @subpackage WebTester - */ - class HttpHeaderPatternExpectation extends HttpHeaderExpectation { - - /** - * Sets the field and value to compare against. - * @param string $header Case insenstive header name. - * @param string $pattern Pattern to compare value against. - * @access public - */ - function HttpHeaderPatternExpectation($header, $pattern) { - $this->HttpHeaderExpectation($header, $pattern); - } - - /** - * Tests the value part of the header. - * @param string $value Value to test. - * @param mixed $pattern Pattern to test against. - * @return boolean True if matched. - * @access protected - */ - function _testHeaderValue($value, $expected) { - return (boolean)preg_match($expected, trim($value)); - } - } /** * Test for a text substring. * @package SimpleTest * @subpackage UnitTester */ - class WantedTextExpectation extends SimpleExpectation { + class TextExpectation extends SimpleExpectation { protected $_substring; /** @@ -330,7 +316,7 @@ * @param string $message Customised message on failure. * @access public */ - function WantedTextExpectation($substring, $message = '%s') { + function TextExpectation($substring, $message = '%s') { $this->SimpleExpectation($message); $this->_substring = $substring; } @@ -395,7 +381,7 @@ * @package SimpleTest * @subpackage UnitTester */ - class UnwantedTextExpectation extends WantedTextExpectation { + class NoTextExpectation extends TextExpectation { /** * Sets the reject pattern @@ -403,8 +389,8 @@ * @param string $message Customised message on failure. * @access public */ - function UnwantedTextExpectation($substring, $message = '%s') { - $this->WantedTextExpectation($substring, $message); + function NoTextExpectation($substring, $message = '%s') { + $this->TextExpectation($substring, $message); } /** @@ -437,36 +423,6 @@ } } - /** - * Extension that builds a web browser at the start of each - * test. - * @package SimpleTest - * @subpackage WebTester - */ - class WebTestCaseInvoker extends SimpleInvokerDecorator { - - /** - * Takes in the test case and reporter to mediate between. - * @param SimpleTestCase $test_case Test case to run. - * @param SimpleScorer $scorer Reporter to receive events. - */ - function WebTestCaseInvoker($invoker) { - $this->SimpleInvokerDecorator($invoker); - } - - /** - * Builds the browser and runs the test. - * @param string $method Test method to call. - * @access public - */ - function invoke($method) { - $test = $this->getTestCase(); - $test->setBrowser($test->createBrowser()); - parent::invoke($method); - $test->unsetBrowser(); - } - } - /** * Test case for testing of web pages. Allows * fetching of pages, parsing of HTML and @@ -476,6 +432,7 @@ */ class WebTestCase extends SimpleTestCase { protected $_browser; + protected $_ignore_errors = false; /** * Creates an empty test case. Should be subclassed @@ -489,13 +446,23 @@ } /** - * Sets the invoker to one that restarts the browser on - * each request. - * @return SimpleInvoker Invoker for each method. + * Announces the start of the test. + * @param string $method Test method just started. * @access public */ - function createInvoker() { - return new WebTestCaseInvoker(parent::createInvoker()); + 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); } /** @@ -505,7 +472,7 @@ * @return SimpleBrowser Current test browser object. * @access public */ - function getBrowser() { + function &getBrowser() { return $this->_browser; } @@ -535,8 +502,9 @@ * @return TestBrowser New browser. * @access public */ - function createBrowser() { - return new SimpleBrowser(); + function &createBrowser() { + $browser = new SimpleBrowser(); + return $browser; } /** @@ -581,6 +549,14 @@ 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. @@ -618,6 +594,42 @@ 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. @@ -673,15 +685,11 @@ * the base URL reflects the new location. * @param string $url URL to fetch. * @param hash $parameters Optional additional GET data. - * @return boolean True on success. + * @return boolean/string Raw page on success. * @access public */ function get($url, $parameters = false) { - $content = $this->_browser->get($url, $parameters); - if ($content === false) { - return false; - } - return true; + return $this->_failOnError($this->_browser->get($url, $parameters)); } /** @@ -691,15 +699,11 @@ * the base URL reflects the new location. * @param string $url URL to fetch. * @param hash $parameters Optional additional GET data. - * @return boolean True on success. + * @return boolean/string Raw page on success. * @access public */ function post($url, $parameters = false) { - $content = $this->_browser->post($url, $parameters); - if ($content === false) { - return false; - } - return true; + return $this->_failOnError($this->_browser->post($url, $parameters)); } /** @@ -711,7 +715,7 @@ * @access public */ function head($url, $parameters = false) { - return $this->_browser->head($url, $parameters); + return $this->_failOnError($this->_browser->head($url, $parameters)); } /** @@ -721,7 +725,7 @@ * @access public */ function retry() { - return $this->_browser->retry(); + return $this->_failOnError($this->_browser->retry()); } /** @@ -732,7 +736,7 @@ * @access public */ function back() { - return $this->_browser->back(); + return $this->_failOnError($this->_browser->back()); } /** @@ -743,7 +747,7 @@ * @access public */ function forward() { - return $this->_browser->forward(); + return $this->_failOnError($this->_browser->forward()); } /** @@ -751,13 +755,14 @@ * for the current realm. * @param string $username Username for realm. * @param string $password Password for realm. - * @return boolean True if successful fetch. Note + * @return boolean/string HTML on successful fetch. Note * that authentication may still have * failed. * @access public */ function authenticate($username, $password) { - return $this->_browser->authenticate($username, $password); + return $this->_failOnError( + $this->_browser->authenticate($username, $password)); } /** @@ -824,17 +829,29 @@ 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. + * @return boolean/string Page on success, else false. * @access public */ function clickSubmit($label = 'Submit', $additional = false) { - return $this->_browser->clickSubmit($label, $additional); + return $this->_failOnError( + $this->_browser->clickSubmit($label, $additional)); } /** @@ -846,7 +863,8 @@ * @access public */ function clickSubmitByName($name, $additional = false) { - return $this->_browser->clickSubmitByName($name, $additional); + return $this->_failOnError( + $this->_browser->clickSubmitByName($name, $additional)); } /** @@ -858,7 +876,8 @@ * @access public */ function clickSubmitById($id, $additional = false) { - return $this->_browser->clickSubmitById($id, $additional); + return $this->_failOnError( + $this->_browser->clickSubmitById($id, $additional)); } /** @@ -875,7 +894,8 @@ * @access public */ function clickImage($label, $x = 1, $y = 1, $additional = false) { - return $this->_browser->clickImage($label, $x, $y, $additional); + return $this->_failOnError( + $this->_browser->clickImage($label, $x, $y, $additional)); } /** @@ -892,7 +912,8 @@ * @access public */ function clickImageByName($name, $x = 1, $y = 1, $additional = false) { - return $this->_browser->clickImageByName($name, $x, $y, $additional); + return $this->_failOnError( + $this->_browser->clickImageByName($name, $x, $y, $additional)); } /** @@ -908,7 +929,8 @@ * @access public */ function clickImageById($id, $x = 1, $y = 1, $additional = false) { - return $this->_browser->clickImageById($id, $x, $y, $additional); + return $this->_failOnError( + $this->_browser->clickImageById($id, $x, $y, $additional)); } /** @@ -919,7 +941,7 @@ * @access public */ function submitFormById($id) { - return $this->_browser->submitFormById($id); + return $this->_failOnError($this->_browser->submitFormById($id)); } /** @@ -933,7 +955,7 @@ * @access public */ function clickLink($label, $index = 0) { - return $this->_browser->clickLink($label, $index); + return $this->_failOnError($this->_browser->clickLink($label, $index)); } /** @@ -943,7 +965,41 @@ * @access public */ function clickLinkById($id) { - return $this->_browser->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); } /** @@ -1006,6 +1062,18 @@ 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. @@ -1013,8 +1081,8 @@ * @return boolean True if field exists, otherwise false. * @access public */ - function setField($name, $value) { - return $this->_browser->setField($name, $value); + function setFieldByName($name, $value) { + return $this->_browser->setFieldByName($name, $value); } /** @@ -1041,18 +1109,27 @@ * @return boolean True if pass. * @access public */ - function assertField($name, $expected = true, $message = "%s") { - $value = $this->_browser->getField($name); - if ($expected === true) { - return $this->assertTrue( - isset($value), - sprintf($message, "Field [$name] should exist")); - } else { - return $this->assertExpectation( - new FieldExpectation($expected), - $value, - sprintf($message, "Field [$name] should match with [%s]")); - } + 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); } /** @@ -1068,18 +1145,33 @@ * @return boolean True if pass. * @access public */ - function assertFieldById($id, $expected = true, $message = "%s") { + 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 of ID [$id] should exist")); - } else { - return $this->assertExpectation( - new FieldExpectation($expected), - $value, - sprintf($message, "Field of ID [$id] should match with [%s]")); + 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); } /** @@ -1160,39 +1252,38 @@ * @access public */ function assertRealm($realm, $message = '%s') { - $message = sprintf($message, "Expected realm [$realm] got [" . - $this->_browser->getRealm() . "]"); - return $this->assertTrue( - strtolower($this->_browser->getRealm()) == strtolower($realm), - $message); + 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 string $value Case sensitive trimmed string to - * match against. + * @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->assertExpectation( + return $this->assert( new HttpHeaderExpectation($header, $value), $this->_browser->getHeaders(), $message); } /** - * Checks each header line for the required pattern. - * @param string $header Case insensitive header name. - * @param string $pattern Pattern to match value against. - * @return boolean True if pass. - * @access public + * @deprecated */ function assertHeaderPattern($header, $pattern, $message = '%s') { - return $this->assertExpectation( - new HttpHeaderPatternExpectation($header, $pattern), + return $this->assert( + new HttpHeaderExpectation($header, new PatternExpectation($pattern)), $this->_browser->getHeaders(), $message); } @@ -1206,26 +1297,32 @@ * @return boolean True if pass. * @access public */ - function assertNoUnwantedHeader($header, $message = '%s') { - return $this->assertExpectation( - new HttpUnwantedHeaderExpectation($header), + 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 or empty - * if expecting no title. - * @param string $message Message to display. - * @return boolean True if pass. + * @param string $title Expected title. + * @param string $message Message to display. + * @return boolean True if pass. * @access public */ function assertTitle($title = false, $message = '%s') { - return $this->assertTrue( - $title === $this->_browser->getTitle(), - sprintf($message, "Expecting title [$title] got [" . - $this->_browser->getTitle() . "]")); + if (! SimpleExpectation::isExpectation($title)) { + $title = new EqualExpectation($title); + } + return $this->assert($title, $this->_browser->getTitle(), $message); } /** @@ -1236,13 +1333,20 @@ * @return boolean True if pass. * @access public */ - function assertWantedText($text, $message = '%s') { - return $this->assertExpectation( - new WantedTextExpectation($text), + 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. @@ -1251,13 +1355,20 @@ * @return boolean True if pass. * @access public */ - function assertNoUnwantedText($text, $message = '%s') { - return $this->assertExpectation( - new UnwantedTextExpectation($text), + 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. @@ -1267,13 +1378,20 @@ * @return boolean True if pass. * @access public */ - function assertWantedPattern($pattern, $message = '%s') { - return $this->assertExpectation( - new WantedPatternExpectation($pattern), + 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. @@ -1283,13 +1401,20 @@ * @return boolean True if pass. * @access public */ - function assertNoUnwantedPattern($pattern, $message = '%s') { - return $this->assertExpectation( - new UnwantedPatternExpectation($pattern), + 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. @@ -1302,15 +1427,15 @@ */ function assertCookie($name, $expected = false, $message = '%s') { $value = $this->getCookie($name); - if ($expected) { - return $this->assertTrue($value === $expected, sprintf( - $message, - "Expecting cookie [$name] value [$expected], got [$value]")); - } else { + 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"); } /** diff --git a/tests/test_tools/simpletest/xml.php b/tests/test_tools/simpletest/xml.php index dd007d41..0d578751 100644 --- a/tests/test_tools/simpletest/xml.php +++ b/tests/test_tools/simpletest/xml.php @@ -3,7 +3,7 @@ * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester - * @version $Id: xml.php,v 1.20 2004/08/04 22:09:39 lastcraft Exp $ + * @version $Id: xml.php,v 1.22 2006/02/06 06:05:18 lastcraft Exp $ */ /**#@+ @@ -11,7 +11,7 @@ */ require_once(dirname(__FILE__) . '/scorer.php'); /**#@-*/ - + /** * Creates the XML needed for remote communication * by SimpleTest. @@ -21,7 +21,7 @@ class XmlReporter extends SimpleReporter { protected $_indent; protected $_namespace; - + /** * Does nothing yet. * @access public @@ -31,7 +31,7 @@ $this->_namespace = ($namespace ? $namespace . ':' : ''); $this->_indent = $indent; } - + /** * Calculates the pretty printing indent level * from the current level of nesting. @@ -44,7 +44,7 @@ $this->_indent, count($this->getTestList()) + $offset); } - + /** * Converts character string to parsed XML * entities string. @@ -58,7 +58,7 @@ array('&', '<', '>', '"', '''), $text); } - + /** * Paints the start of a group test. * @param string $test_name Name of test that is starting. @@ -74,7 +74,7 @@ $this->toParsedXml($test_name) . "_namespace . "name>\n"; } - + /** * Paints the end of a group test. * @param string $test_name Name of test that is ending. @@ -85,7 +85,7 @@ print "_namespace . "group>\n"; parent::paintGroupEnd($test_name); } - + /** * Paints the start of a test case. * @param string $test_name Name of test that is starting. @@ -100,7 +100,7 @@ $this->toParsedXml($test_name) . "_namespace . "name>\n"; } - + /** * Paints the end of a test case. * @param string $test_name Name of test that is ending. @@ -111,7 +111,7 @@ print "_namespace . "case>\n"; parent::paintCaseEnd($test_name); } - + /** * Paints the start of a test method. * @param string $test_name Name of test that is starting. @@ -126,7 +126,7 @@ $this->toParsedXml($test_name) . "_namespace . "name>\n"; } - + /** * Paints the end of a test method. * @param string $test_name Name of test that is ending. @@ -138,7 +138,7 @@ print "_namespace . "test>\n"; parent::paintMethodEnd($test_name); } - + /** * Increments the pass count. * @param string $message Message is ignored. @@ -151,7 +151,7 @@ print $this->toParsedXml($message); print "_namespace . "pass>\n"; } - + /** * Increments the fail count. * @param string $message Message is ignored. @@ -164,15 +164,15 @@ print $this->toParsedXml($message); print "_namespace . "fail>\n"; } - + /** * Paints a PHP error or exception. * @param string $message Message is ignored. * @access public * @abstract */ - function paintException($message) { - parent::paintException($message); + function paintError($message) { + parent::paintError($message); print $this->_getIndent(1); print "<" . $this->_namespace . "exception>"; print $this->toParsedXml($message); @@ -191,7 +191,7 @@ print $this->toParsedXml($message); print "_namespace . "message>\n"; } - + /** * Paints a formatted ASCII message such as a * variable dump. @@ -239,7 +239,7 @@ print "?>\n"; print "<" . $this->_namespace . "run>\n"; } - + /** * Paints the test document footer. * @param string $test_name The top level test. @@ -250,7 +250,7 @@ print "_namespace . "run>\n"; } } - + /** * Accumulator for incoming tag. Holds the * incoming test structure information for @@ -261,7 +261,7 @@ class NestingXmlTag { protected $_name; protected $_attributes; - + /** * Sets the basic test information except * the name. @@ -272,7 +272,7 @@ $this->_name = false; $this->_attributes = $attributes; } - + /** * Sets the test case/method name. * @param string $name Name of test. @@ -281,7 +281,7 @@ function setName($name) { $this->_name = $name; } - + /** * Accessor for name. * @return string Name of test. @@ -290,7 +290,7 @@ function getName() { return $this->_name; } - + /** * Accessor for attributes. * @return hash All attributes. @@ -300,7 +300,7 @@ return $this->_attributes; } } - + /** * Accumulator for incoming method tag. Holds the * incoming test structure information for @@ -309,7 +309,7 @@ * @subpackage UnitTester */ class NestingMethodTag extends NestingXmlTag { - + /** * Sets the basic test information except * the name. @@ -319,7 +319,7 @@ function NestingMethodTag($attributes) { $this->NestingXmlTag($attributes); } - + /** * Signals the appropriate start event on the * listener. @@ -328,8 +328,8 @@ */ function paintStart($listener) { $listener->paintMethodStart($this->getName()); - } - + } + /** * Signals the appropriate end event on the * listener. @@ -340,7 +340,7 @@ $listener->paintMethodEnd($this->getName()); } } - + /** * Accumulator for incoming case tag. Holds the * incoming test structure information for @@ -349,7 +349,7 @@ * @subpackage UnitTester */ class NestingCaseTag extends NestingXmlTag { - + /** * Sets the basic test information except * the name. @@ -359,7 +359,7 @@ function NestingCaseTag($attributes) { $this->NestingXmlTag($attributes); } - + /** * Signals the appropriate start event on the * listener. @@ -368,8 +368,8 @@ */ function paintStart($listener) { $listener->paintCaseStart($this->getName()); - } - + } + /** * Signals the appropriate end event on the * listener. @@ -380,7 +380,7 @@ $listener->paintCaseEnd($this->getName()); } } - + /** * Accumulator for incoming group tag. Holds the * incoming test structure information for @@ -389,7 +389,7 @@ * @subpackage UnitTester */ class NestingGroupTag extends NestingXmlTag { - + /** * Sets the basic test information except * the name. @@ -409,7 +409,7 @@ function paintStart($listener) { $listener->paintGroupStart($this->getName(), $this->getSize()); } - + /** * Signals the appropriate end event on the * listener. @@ -419,7 +419,7 @@ function paintEnd($listener) { $listener->paintGroupEnd($this->getName()); } - + /** * The size in the attributes. * @return integer Value of size attribute or zero. @@ -433,7 +433,7 @@ return 0; } } - + /** * Parser for importing the output of the XmlReporter. * Dispatches that output to another reporter. @@ -447,7 +447,7 @@ protected $_in_content_tag; protected $_content; protected $_attributes; - + /** * Loads a listener with the SimpleReporter * interface. @@ -462,7 +462,7 @@ $this->_content = ''; $this->_attributes = array(); } - + /** * Parses a block of XML sending the results to * the listener. @@ -478,13 +478,13 @@ } return true; } - + /** * Sets up expat as the XML parser. * @return resource Expat handle. * @access protected */ - function _createParser() { + function &_createParser() { $expat = xml_parser_create(); xml_set_object($expat, $this); xml_set_element_handler($expat, '_startElement', '_endElement'); @@ -492,7 +492,7 @@ xml_set_default_handler($expat, '_default'); return $expat; } - + /** * Opens a new test nesting level. * @return NestedXmlTag The group, case or method tag @@ -502,17 +502,17 @@ 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() { + function &_getCurrentNestingTag() { return $this->_tag_stack[0]; } - + /** * Ends a nesting tag. * @return NestedXmlTag The group, case or method tag @@ -522,7 +522,7 @@ 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. @@ -530,9 +530,8 @@ * @private */ function _isLeaf($tag) { - return in_array( - $tag, - array('NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL')); + return in_array($tag, array( + 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL')); } /** @@ -557,7 +556,7 @@ $this->_content = ''; } } - + /** * End of element event. * @param resource $expat Parser handle. @@ -578,7 +577,7 @@ } elseif ($tag == 'FAIL') { $this->_listener->paintFail($this->_content); } elseif ($tag == 'EXCEPTION') { - $this->_listener->paintException($this->_content); + $this->_listener->paintError($this->_content); } elseif ($tag == 'SIGNAL') { $this->_listener->paintSignal( $this->_attributes['TYPE'], @@ -589,7 +588,7 @@ $this->_listener->paintFormattedMessage($this->_content); } } - + /** * Content between start and end elements. * @param resource $expat Parser handle. @@ -602,7 +601,7 @@ } return true; } - + /** * XML and Doctype handler. Discards all such content. * @param resource $expat Parser handle. -- cgit v1.2.3