summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwei <>2006-07-14 06:56:16 +0000
committerwei <>2006-07-14 06:56:16 +0000
commit143980b6dab8ad87c44518e5b7befb614fb83b85 (patch)
tree6afadfb80ca6e4d36443f83bd5a6cc2cfe73032d
parentc004bbdf4f0e824e5ccbaef8f98ca4a3d44d3b49 (diff)
Add time-tracker sample and docs. (Incomplete)
-rw-r--r--.gitattributes38
-rw-r--r--demos/quickstart/themes/PradoSoft/style.css58
-rw-r--r--demos/time-tracker/index.php20
-rw-r--r--demos/time-tracker/protected/.htaccess1
-rw-r--r--demos/time-tracker/protected/APP_CODE/BaseDao.php18
-rw-r--r--demos/time-tracker/protected/APP_CODE/Project.php17
-rw-r--r--demos/time-tracker/protected/APP_CODE/ProjectDao.php76
-rw-r--r--demos/time-tracker/protected/APP_CODE/TimeTrackerException.php14
-rw-r--r--demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php31
-rw-r--r--demos/time-tracker/protected/APP_CODE/exceptions.txt3
-rw-r--r--demos/time-tracker/protected/controls/Layout.php7
-rw-r--r--demos/time-tracker/protected/controls/Layout.tpl45
-rw-r--r--demos/time-tracker/protected/controls/TopicList.php8
-rw-r--r--demos/time-tracker/protected/controls/TopicList.tpl27
-rw-r--r--demos/time-tracker/protected/data/time_tracker.dbbin0 -> 16384 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page127
-rw-r--r--demos/time-tracker/protected/pages/Docs/GettingStarted.page67
-rw-r--r--demos/time-tracker/protected/pages/Docs/Home.page10
-rw-r--r--demos/time-tracker/protected/pages/Docs/Introduction.page64
-rw-r--r--demos/time-tracker/protected/pages/Docs/MoreTests.page140
-rw-r--r--demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page202
-rw-r--r--demos/time-tracker/protected/pages/Docs/UsingSQLMap.page210
-rw-r--r--demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page39
-rw-r--r--demos/time-tracker/protected/pages/Docs/WritingUnitTest.page73
-rw-r--r--demos/time-tracker/protected/pages/Docs/config.xml8
-rw-r--r--demos/time-tracker/protected/pages/Docs/db.pngbin0 -> 26879 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/functional_test1.pngbin0 -> 92502 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/functional_test2.pngbin0 -> 104726 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/preface.page19
-rw-r--r--demos/time-tracker/protected/pages/Docs/project1.pngbin0 -> 338441 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/unit_test1.pngbin0 -> 55855 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/unit_test2.pngbin0 -> 87439 bytes
-rw-r--r--demos/time-tracker/protected/pages/Docs/unit_test3.pngbin0 -> 61923 bytes
-rw-r--r--demos/time-tracker/tests/functional.php10
-rw-r--r--demos/time-tracker/tests/functional/HelloPradoTestCase.php14
-rw-r--r--demos/time-tracker/tests/unit.php11
-rw-r--r--demos/time-tracker/tests/unit/AddUserToProjectTestCase.php35
-rw-r--r--demos/time-tracker/tests/unit/CreateNewProjectTestCase.php90
-rw-r--r--demos/time-tracker/tests/unit/ProjectDaoTestCase.php92
-rw-r--r--demos/time-tracker/tests/unit/ProjectTestCase.php15
40 files changed, 1589 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
index 6ec23882..565f4fa6 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1066,6 +1066,44 @@ demos/sqlmap-sample/tests/PersonTest.php -text
demos/sqlmap-sample/tests/readme.txt -text
demos/sqlmap-sample/tests/run_tests.php -text
demos/sqlmap-sample/tests/sqlmap.xml -text
+demos/time-tracker/index.php -text
+demos/time-tracker/protected/.htaccess -text
+demos/time-tracker/protected/APP_CODE/BaseDao.php -text
+demos/time-tracker/protected/APP_CODE/Project.php -text
+demos/time-tracker/protected/APP_CODE/ProjectDao.php -text
+demos/time-tracker/protected/APP_CODE/TimeTrackerException.php -text
+demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php -text
+demos/time-tracker/protected/APP_CODE/exceptions.txt -text
+demos/time-tracker/protected/controls/Layout.php -text
+demos/time-tracker/protected/controls/Layout.tpl -text
+demos/time-tracker/protected/controls/TopicList.php -text
+demos/time-tracker/protected/controls/TopicList.tpl -text
+demos/time-tracker/protected/data/time_tracker.db -text
+demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page -text
+demos/time-tracker/protected/pages/Docs/GettingStarted.page -text
+demos/time-tracker/protected/pages/Docs/Home.page -text
+demos/time-tracker/protected/pages/Docs/Introduction.page -text
+demos/time-tracker/protected/pages/Docs/MoreTests.page -text
+demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page -text
+demos/time-tracker/protected/pages/Docs/UsingSQLMap.page -text
+demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page -text
+demos/time-tracker/protected/pages/Docs/WritingUnitTest.page -text
+demos/time-tracker/protected/pages/Docs/config.xml -text
+demos/time-tracker/protected/pages/Docs/db.png -text
+demos/time-tracker/protected/pages/Docs/functional_test1.png -text
+demos/time-tracker/protected/pages/Docs/functional_test2.png -text
+demos/time-tracker/protected/pages/Docs/preface.page -text
+demos/time-tracker/protected/pages/Docs/project1.png -text
+demos/time-tracker/protected/pages/Docs/unit_test1.png -text
+demos/time-tracker/protected/pages/Docs/unit_test2.png -text
+demos/time-tracker/protected/pages/Docs/unit_test3.png -text
+demos/time-tracker/tests/functional.php -text
+demos/time-tracker/tests/functional/HelloPradoTestCase.php -text
+demos/time-tracker/tests/unit.php -text
+demos/time-tracker/tests/unit/AddUserToProjectTestCase.php -text
+demos/time-tracker/tests/unit/CreateNewProjectTestCase.php -text
+demos/time-tracker/tests/unit/ProjectDaoTestCase.php -text
+demos/time-tracker/tests/unit/ProjectTestCase.php -text
docs/application.xml -text
docs/specs/application.dtd -text
docs/specs/application.xsd -text
diff --git a/demos/quickstart/themes/PradoSoft/style.css b/demos/quickstart/themes/PradoSoft/style.css
index 782697ae..d7e94032 100644
--- a/demos/quickstart/themes/PradoSoft/style.css
+++ b/demos/quickstart/themes/PradoSoft/style.css
@@ -517,4 +517,62 @@ pre code
background-color:#ffffee;
font-family: "Courier New", Courier, mono;
margin: 0.2em;
+}
+
+div.tip, div.info, div.note
+{
+ border:1px solid #0cf;
+ padding:1em;
+ margin: 1em 2em;
+ background-color: #eff;
+}
+
+div.info
+{
+ border-color: #32CD32;
+ background-color: #EBFFCE;
+}
+
+div.note
+{
+ border-color: Orange;
+ background-color: #FFF5E1;
+}
+
+div b.tip
+{
+ font-size: 1em;
+ padding-right: 0.5em;
+}
+
+img.figure
+{
+ display: block;
+ margin: 1em auto;
+ background-color: White;
+ padding: 15px;
+ border: 1px solid #eee;
+}
+
+div.caption
+{
+ text-align: center;
+}
+
+table.tabular, table.tabular td, table.tabular th
+{
+ border: 1px solid #ccc;
+ border-collapse: collapse;
+ padding: 0.3em;
+}
+
+table.tabular
+{
+ margin: 1em auto;
+ width: 80%;
+}
+
+table.tabular td
+{
+ padding: 0.75em;
} \ No newline at end of file
diff --git a/demos/time-tracker/index.php b/demos/time-tracker/index.php
new file mode 100644
index 00000000..afada2c1
--- /dev/null
+++ b/demos/time-tracker/index.php
@@ -0,0 +1,20 @@
+<?php
+
+$basePath=dirname(__FILE__);
+$frameworkPath='../../framework/prado.php';
+$assetsPath=$basePath."/assets";
+$runtimePath=$basePath."/protected/runtime";
+
+if(!is_file($frameworkPath))
+ die("Unable to find prado framework path $frameworkPath.");
+if(!is_writable($assetsPath))
+ die("Please make sure that the directory $assetsPath is writable by Web server process.");
+if(!is_writable($runtimePath))
+ die("Please make sure that the directory $runtimePath is writable by Web server process.");
+
+require_once($frameworkPath);
+
+$application=new TApplication;
+$application->run();
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/.htaccess b/demos/time-tracker/protected/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/demos/time-tracker/protected/.htaccess
@@ -0,0 +1 @@
+deny from all \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/BaseDao.php b/demos/time-tracker/protected/APP_CODE/BaseDao.php
new file mode 100644
index 00000000..f9146b59
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/BaseDao.php
@@ -0,0 +1,18 @@
+<?php
+
+class BaseDao
+{
+ private $_connection;
+
+ public function setConnection($connection)
+ {
+ $this->_connection = $connection;
+ }
+
+ protected function getConnection()
+ {
+ return $this->_connection;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/Project.php b/demos/time-tracker/protected/APP_CODE/Project.php
new file mode 100644
index 00000000..ad9f7d19
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/Project.php
@@ -0,0 +1,17 @@
+<?php
+
+class Project
+{
+ public $ActualDuration = 0;
+ public $CreatorUserName = '';
+ public $CompletionDate = 0;
+ public $DateCreated = 0;
+ public $Description = '';
+ public $EstimateDuration = 0;
+ public $ID = 0;
+ public $ManagerUserName = '';
+ public $Name = '';
+}
+
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/ProjectDao.php b/demos/time-tracker/protected/APP_CODE/ProjectDao.php
new file mode 100644
index 00000000..25a2845d
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/ProjectDao.php
@@ -0,0 +1,76 @@
+<?php
+
+Prado::using('Application.APP_CODE.BaseDao');
+
+class ProjectDao extends BaseDao
+{
+ public function createNewProject($project)
+ {
+ $sqlmap = $this->getConnection();
+ $creator = $sqlmap->queryForObject('GetUserByName', $project->CreatorUserName);
+ $manager = $sqlmap->queryForObject('GetUserByName', $project->ManagerUserName);
+ $exists = $sqlmap->queryForObject('GetProjectByName', $project->Name);
+ if($exists)
+ {
+ throw new TimeTrackerException(
+ 'project_exists', $project->Name);
+ }
+ else if(!$creator || !$manager)
+ {
+ throw new TimeTrackerException(
+ 'invalid_creator_and_manager',
+ $project->Name, $project->CreatorUserName,
+ $project->ManagerUserName);
+ }
+ else
+ {
+ $param['project'] = $project;
+ $param['creator'] = $creator->ID;
+ $param['manager'] = $manager->ID;
+ return $sqlmap->insert('CreateNewProject', $param);
+ }
+ }
+
+ public function getProjectByID($projectID)
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->queryForObject('GetProjectByID', $projectID);
+ }
+
+ public function addUserToProject($project, $user)
+ {
+ $sqlmap = $this->getConnection();
+ $project = $this->getProjectByID($project->ID);
+ $user = $sqlmap->queryForObject('GetUserByName', $user->Name);
+ $list = $sqlmap->queryForList('GetProjectMembers', $project);
+ $userExists = false;
+ foreach($list as $k)
+ {
+ if($k->ID == $user->ID)
+ $userExists = true;
+ }
+ if(!$project)
+ {
+ throw new TimeTrackerException(
+ 'invalid_project', $project->Name);
+ }
+ else if(!$user)
+ {
+ throw new TimeTrackerException(
+ 'invalid_user', $user->Name);
+ }
+ else if($userExists)
+ {
+ throw new TimeTrackerException(
+ 'project_member_exists', $projet->Name, $user->Name);
+ }
+ else
+ {
+ $param['project'] = $project;
+ $param['user'] = $user;
+ return $sqlmap->insert('AddUserToProject', $param);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php b/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php
new file mode 100644
index 00000000..d715eefa
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/TimeTrackerException.php
@@ -0,0 +1,14 @@
+<?php
+
+class TimeTrackerException extends TException
+{
+ /**
+ * @return string path to the error message file
+ */
+ protected function getErrorMessageFile()
+ {
+ return dirname(__FILE__).'/exceptions.txt';
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php b/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php
new file mode 100644
index 00000000..4b6987bd
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/TimeTrackerUser.php
@@ -0,0 +1,31 @@
+<?php
+
+Prado::using('System.Security.TUser');
+Prado::using('System.Security.TUserManager');
+
+class TimeTrackerUser extends TUser
+{
+ private $_ID;
+
+ public function __construct()
+ {
+ parent::__construct(new TUserManager());
+ }
+
+ public function getID(){ return $this->_ID; }
+ public function setID($value)
+ {
+ if(is_null($this->_ID))
+ $this->_ID = $value;
+ else
+ throw new TimeTrackerUserException(
+ 'timetracker_user_readonly_id');
+ }
+}
+
+class TimeTrackerUserException extends TimeTrackerException
+{
+
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/APP_CODE/exceptions.txt b/demos/time-tracker/protected/APP_CODE/exceptions.txt
new file mode 100644
index 00000000..e948f4d0
--- /dev/null
+++ b/demos/time-tracker/protected/APP_CODE/exceptions.txt
@@ -0,0 +1,3 @@
+timetracker_user_readonly_id = Time tracker user ID is read-only.
+invalid_creator_and_manager = Unable to find time tracker usernames '{1}' and '{2}' for project '{0}'.
+project_exists = Project '{0}' already exists. \ No newline at end of file
diff --git a/demos/time-tracker/protected/controls/Layout.php b/demos/time-tracker/protected/controls/Layout.php
new file mode 100644
index 00000000..e612d52d
--- /dev/null
+++ b/demos/time-tracker/protected/controls/Layout.php
@@ -0,0 +1,7 @@
+<?php
+
+class Layout extends TTemplateControl
+{
+
+}
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/controls/Layout.tpl b/demos/time-tracker/protected/controls/Layout.tpl
new file mode 100644
index 00000000..95c88fd8
--- /dev/null
+++ b/demos/time-tracker/protected/controls/Layout.tpl
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
+
+<com:THead Title="PRADO Time Tracker Guide">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<meta http-equiv="content-language" content="en"/>
+</com:THead>
+
+<body>
+<com:TForm>
+<div id="header">
+<div class="title">PRADO Time Tracker Guide</div>
+<div class="image"></div>
+</div>
+
+<com:TPanel ID="MainMenu" CssClass="mainmenu">
+<a href="?">Home</a> |
+<a href="http://www.pradosoft.com">PradoSoft.com</a> |
+<a href="../../docs/quickstart.pdf">PDF Version</a> |
+<com:THyperLink ID="PrinterLink" Text="Printer-friendly Version" />
+</com:TPanel>
+
+<table width="100%" border="0" cellspacing="0" cellpadding="0">
+<tr>
+<td valign="top" width="1">
+<com:Application.controls.TopicList ID="TopicPanel" />
+</td>
+<td valign="top">
+<div id="content">
+<com:TContentPlaceHolder ID="body" />
+</div>
+</td>
+</tr>
+</table>
+
+<div id="footer">
+Copyright &copy; 2005-2006 <a href="http://www.pradosoft.com">PradoSoft</a>.
+<br/><br/>
+
+<a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0 Transitional</a>
+</div>
+
+</com:TForm>
+</body>
+</html> \ No newline at end of file
diff --git a/demos/time-tracker/protected/controls/TopicList.php b/demos/time-tracker/protected/controls/TopicList.php
new file mode 100644
index 00000000..ce827cc0
--- /dev/null
+++ b/demos/time-tracker/protected/controls/TopicList.php
@@ -0,0 +1,8 @@
+<?php
+
+class TopicList extends TTemplateControl
+{
+
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/protected/controls/TopicList.tpl b/demos/time-tracker/protected/controls/TopicList.tpl
new file mode 100644
index 00000000..bc9fff26
--- /dev/null
+++ b/demos/time-tracker/protected/controls/TopicList.tpl
@@ -0,0 +1,27 @@
+<div id="toc">
+
+<div class="topic">
+<ul>
+ <li><a href="?page=preface">Preface</a></li>
+</ul>
+<div>Prado Time Tracker Implementation Guide</div>
+<ul>
+ <li><a href="?page=Introduction">Introduction</a></li>
+ <li><a href="?page=GettingStarted">Installation</a></li>
+ <li><a href="?page=WritingUnitTest">Writing a Unit Test</a></li>
+ <li><a href="?page=WritingFunctionalTest">Writing a Functional Web Test</a></li>
+</ul>
+
+<div>Testing Business Code</div>
+<ul>
+ <li><a href="?page=CreateBusinessCode">Create Business Code</a></li>
+ <li><a href="?page=UsingSQLMap">Using SQLMap Data Mapper</a></li>
+ <li><a href="?page=UserClassAndExceptions">User Class and Exceptions</a></li>
+ <li><a href="?page=MoreTests">More Tests</a></li>
+</ul>
+
+</div>
+
+
+
+</div> \ No newline at end of file
diff --git a/demos/time-tracker/protected/data/time_tracker.db b/demos/time-tracker/protected/data/time_tracker.db
new file mode 100644
index 00000000..03fe9156
--- /dev/null
+++ b/demos/time-tracker/protected/data/time_tracker.db
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page b/demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page
new file mode 100644
index 00000000..e5afa572
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/CreateBusinessCode.page
@@ -0,0 +1,127 @@
+<com:TContent ID="body">
+<h1>Create Business Code</h1>
+<p>We start the design with the database, the entity relationships are shown
+in the diagram below.</p>
+
+<img src=<%~ db.png %> class="figure" />
+
+<p>Now we can begin to create and test some business code. Let us begin
+with the <tt>Project</tt> defintions. First, we add some properties or fields.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+class Project
+{
+ public $ActualDuration = 0;
+ public $CreatorUserName = '';
+ public $CompletionDate = 0;
+ public $DateCreated = 0;
+ public $Description = '';
+ public $EstimateDuration = 0;
+ public $ID = 0;
+ public $ManagerUserName = '';
+ public $Name = '';
+}
+?&gt;
+</com:TTextHighlighter>
+
+<p>All the fields should be self explainatory. The <tt>ManagerUserName</tt>
+is the user name of the project manager. Notice that the fields
+are public, later on, we can change some or all of them to be private and
+provide some accessor and mutators (i.e. getters and setters). If we want
+we can let the <tt>Project</tt> class inherit <tt>TComponent</tt> such
+that the getters and setters can be used like properties, such as those
+found in Prado.</p>
+
+<h2>Business Services, test case first</h2>
+<p>Next we want to add users to the project. For this, we start with some
+unit tests. We are going to design the business logic around the concept of
+<a href="java">Data Access Objects</a> (DAO).</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+Prado::using('Application.APP_CODE.*');
+class ProjectDaoTestCase extends UnitTestCase
+{
+ function testProjectDaoCanCreateNewProject()
+ {
+ $project = new Project();
+ $project->Name = "Project 1";
+
+ $dao = new ProjectDao();
+
+ $this->assertTrue($dao->createNewProject($project));
+ $this->assertEqual($dao->getProjectByID(1), $project);
+ }
+}
+?&gt;
+</com:TTextHighlighter>
+<p>So what are we doing here? First we create a new <tt>Project</tt> named
+"Project 1". Then we create a new <tt>ProjectDao</tt> so we can insert new projects
+and retrieve it. We assert that a project will be create sucessfully using
+<tt>assertTrue($do->createNewProject(...))</tt>. We also assert that
+<tt>getProjectByID(1)</tt> will return an instance of <tt>Project</tt> class
+with same data (the reference may be different).</p>
+
+<p>If we run the above unit test case, nothing is going to pass since we have
+not even defined the <tt>ProjectDao</tt> class. So lets do that first and import
+the class in the tests as well.</p>
+
+<p>We will create a base Dao class as follows, and we save as <tt>BaseDao.php</tt>
+in our <tt>APP_CODE</tt> directory.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+class BaseDao
+{
+ private $_connection;
+
+ public function setConnection($connection)
+ {
+ $this->_connection = $connection;
+ }
+
+ protected function getConnection()
+ {
+ return $this->_connection;
+ }
+}
+?&gt;
+</com:TTextHighlighter>
+<p>And finally our <tt>ProjectDao</tt> class.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+Prado::using('Application.APP_CODE.BaseDao');
+class ProjectDao extends BaseDao
+{
+}
+?&gt;
+</com:TTextHighlighter>
+<p>If we run the unit test again, we get an error message something like
+"<i>Fatal error: Call to undefined method ProjectDao::createNewProject() in ...</i>".
+So let us, fix this.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+class ProjectDao extends BaseDao
+{
+ public function createNewProject($project)
+ {
+ }
+
+ public function getProjectByID($projectID)
+ {
+ }
+}
+</com:TTextHighlighter>
+<p>Run the unit test again, we see a different type of error now. This time
+the unit test runner complians that the test fails.</p>
+<h2>Write Test, Run Test, Repeat</h2>
+<div class="info">
+<b>At this point, you may notice a repetition</b>
+<ol>
+ <li>write some test code,</li>
+ <li>run the test, test gives an error,</li>
+ <li>write some actual code to only remove the previous error,</li>
+ <li>goto step 1 to repeat.</li>
+</ol>
+</div>
+<p>We shall see how we can make this test pass in the next section.</p>
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/GettingStarted.page b/demos/time-tracker/protected/pages/Docs/GettingStarted.page
new file mode 100644
index 00000000..8b8e8e5e
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/GettingStarted.page
@@ -0,0 +1,67 @@
+<com:TContent ID="body">
+<h1>Installation</h1>
+<p>There are a few pieces of tools and software needed throughout the development
+of the application. First we shall setup the environment for coding and testing. We
+shall from here on assume that you have access to a working installation of a web server with
+PHP and possibilly a MySQL database server. The first thing to do is install Prado and some
+testing tools.</p>
+
+<h2>Download and Install Prado</h2>
+<p>The minimum requirement by PRADO is that the Web server support PHP 5.
+ For the Time Tracker sample application, you need Prado version 3.1 or greater.</p>
+<p>Installation of PRADO mainly involves downloading and unpacking.</p>
+<ol>
+ <li>Go to <a href="http://www.pradosoft.com/download/">pradosoft.com</a> to grab the latest version of PRADO.</li>
+ <li>Unpack the PRADO release file to a Web-accessible directory.</li>
+</ol>
+
+<p>You should at least first check that the demos bundled with the Prado distribution are
+ working. See the <a href="../quickstart/index.php?page=GettingStarted.Installation">
+ quickstart tutorial for further instructions</a> on running the demos.
+</p>
+
+<h2>Help! Nothing is working!</h2>
+<p>If you encounter problems in downloading, unpacking and running the demo applications, please
+ visit the <a href="http://www.pradosoft.com/forum/">forum to seek help</a>. Please
+ do a search on the forum first or <a href="http://www.pradosoft.com/demos/quickstart/prado3_quick_start.pdf">read the PDF version</a> of the quickstart.
+ The friendly and wonderful people at the forum can better assist you if you can provide as much detail regarding your problem.
+ You should include in your post your server configuration details, the steps you took to reproduce your problem, and any error messages encountered.</p>
+
+<h2>Create a new Prado application</h2>
+<p>Prado is bundled with a command line tool to create the necessary directory structure to run a hello world application.
+The command tool <tt>prado-cli.php</tt> is a php script and can be found in the <tt>prado/framework/</tt> directory.
+To create a new application, go to your document root and type the following command in console.
+</p>
+<com:TTextHighlighter Language="none" CssClass="source">
+php prado/framework/prado-cli.php -t -c time-tracker
+</com:TTextHighlighter>
+
+<div class="tip"><b class="tip">Tip:</b>
+For linux and OS X environments, you can <tt>chmod u+x prado-cli.php</tt>
+and change the first line of <tt>prado-cli.php</tt> to the location of your
+PHP command line interpreter. Now, you may call the script from command line
+like an executable.
+</div>
+
+<p>A directory named <tt>time-tracker</tt> will be created containing the following.
+<com:TTextHighlighter Language="none" CssClass="source">
+document_root/time-tracker/assets/
+document_root/time-tracker/index.php
+document_root/time-tracker/protected/
+document_root/time-tracker/protected/.htaccess
+document_root/time-tracker/protected/pages/
+document_root/time-tracker/protected/pages/Home.page
+document_root/time-tracker/protected/runtime/
+</com:TTextHighlighter>
+<p>The <tt>time-tracker</tt> directory is where we are going to put all our code for the Time Tracker application.
+Since <tt>-t</tt> was passed into the <tt>prado-cli.php</tt> script, unit and functional test
+ skeletons are also created.</p>
+<com:TTextHighlighter Language="none" CssClass="source">
+document_root/time-tracker/tests/
+document_root/time-tracker/tests/unit/
+document_root/time-tracker/tests/functional/
+document_root/time-tracker/tests/unit.php
+document_root/time-tracker/tests/functional.php
+</com:TTextHighlighter>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/Home.page b/demos/time-tracker/protected/pages/Docs/Home.page
new file mode 100644
index 00000000..5c7a486b
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/Home.page
@@ -0,0 +1,10 @@
+<com:TContent ID="body">
+<h1>Prado Time Tracker Implementation Guide</h1>
+<ul>
+ <li><a href="?page=Introduction">Introduction</a></li>
+ <li><a href="?page=GettingStarted">Getting Started</a></li>
+ <li><a href="?page=WritingUnitTest">Writing a Unit Test</a></li>
+ <li><a href="?page=WritingFunctionalTest">Writing a Functional Web Test</a></li>
+ <li><a href="?page=CreateBusinessCode ">Create Business Code</a></li>
+</ul>
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/Introduction.page b/demos/time-tracker/protected/pages/Docs/Introduction.page
new file mode 100644
index 00000000..baa305f5
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/Introduction.page
@@ -0,0 +1,64 @@
+<com:TContent ID="body">
+<h1>Introduction</h1>
+
+<p>The Time Tracker is a fully functional sample application designed to
+ introduce you to many of Prado's features. This guide is a step by step
+ walkthrough starting from installation to deployment. The Time Tracker
+ application is based on the
+ <a href="http://www.asp.net/downloads/starterkits/">ASP.NET's Time Tracker Starter Kit</a>.
+</p>
+
+<h1>Time Tracker Overview</h1>
+<p>The Time Tracker is a business web application for keeping track of hours spent
+on a project, with ability to handle multiple resources as well as multiple projects.</p>
+<h2>Basic Application Requirements</h2>
+<p>The functional requirements of the Time Tracker is based on the
+ <a href="http://www.asp.net/downloads/starterkits/">ASP.NET's Time Tracker Starter Kit</a>
+ description.</p>
+<p class="requirements">
+<h3>Create projects</h3>
+<ul>
+ <li>Define projection information like due dates, hours to complete, project
+ resources, and more.</li>
+ <li>Break down projects into tasks and track work on per-task basis.</li>
+</ul>
+<h3>Create and track tasks</h3>
+<ul>
+ <li>Track time spent each day by category and project.</li>
+</ul>
+<h3>Use reports to track progress</h3>
+<ul>
+ <li>Track overall progress across multiple projects, including estimated and actual work.</li>
+ <li>Track total work for team resources across multiple projects.</li>
+</ul>
+</p>
+
+<h2>Technologies and Design Approached Demonstrated</h2>
+<ul>
+ <li>Prado event-driven component framework</li>
+ <li>Separation of concerns: persistent storage, business logic, presentation</li>
+ <li>Object-Relational mapping using SQLMap</li>
+ <li>Unit testing and functional testing</li>
+ <li>Promote code reuse</li>
+</ul>
+
+<h1>Requirements</h1>
+<p>The Time Tracker web application requires the following software and knowledge.</p>
+<h2>Software requirements</h2>
+<p>It is assumed that you are able to obtain and install the following pieces of software.</p>
+<ol>
+ <li>PHP version 5.0.4 or greater</li>
+ <li>A web server, such as Apache, able to run PHP scripts</li>
+ <li>MySQL database server version 4.1 or greater (alternatively SQLite or Postgres)</li>
+ <li>Prado version 3.1 or later</li>
+</ol>
+
+<p>In addition to software requirements, we assumed that you have some of the following knowledge.</p>
+<ul>
+ <li>Access the internet to download packaged code, and unzip these code.</li>
+ <li>Understand the basic concepts of Object-Oriented programming in PHP such as
+ classes, objects, methods, etc.</li>
+ <li>Basic understanding of relational databases and Structured Query Language (SQL).</li>
+ <li>Knows how to have fun, this is mandatory.</li>
+</ul>
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/MoreTests.page b/demos/time-tracker/protected/pages/Docs/MoreTests.page
new file mode 100644
index 00000000..5e598982
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/MoreTests.page
@@ -0,0 +1,140 @@
+<com:TContent ID="body">
+
+<h1>Finishing up test case</h1>
+<p>Up to this point, you should have the follow pieces of files and code.
+<img src=<%~ project1.png %> class="figure" />
+</p>
+
+<p>So what else can we test for at this point? A few reasonable
+tests are to see what happens if the project already exists, and what if
+the username does not exists.</p>
+
+<p>First we shall refactor our test code since much of the setup code
+for the mocked connection will need to be repeated in other test assertions.
+We change the test case to have these three new methods.
+</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+function setupMockConnectionFor($project)
+{
+ $customer = new TimeTrackerUser();
+ $customer->ID = 1;
+ $customer->Name = "Customer A";
+
+ $manager = new TimeTrackerUser();
+ $manager->ID = 2;
+ $manager->Name = "Manager A";
+
+ $conn = $this->connection;
+
+ //return the customer and manager
+ $conn->setReturnValue('queryForObject',
+ $customer, array('GetUserByName', 'Customer A'));
+ $conn->setReturnValue('queryForObject',
+ $manager, array('GetUserByName', 'Manager A'));
+
+ //project does not exist
+ $conn->setReturnValue('queryForObject',
+ null, array('GetProjectByName', 'Project 1'));
+
+ $param['project'] = $project;
+ $param['creator'] = $customer->ID;
+ $param['manager'] = $manager->ID;
+
+ $conn->setReturnValue('insert',
+ true, array('CreateNewProject', $param));
+ $conn->setReturnReference('queryForObject',
+ $project, array('GetProjectByID', 1));
+}
+
+function createNewTestProject()
+{
+ $project = new Project();
+ $project->Name = "Project 1";
+ $project->CreatorUserName = "Customer A";
+ $project->ManagerUserName = "Manager A";
+
+ return $project;
+}
+
+function assertProjectCreated($project)
+{
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+}
+</com:TTextHighlighter>
+
+<p>Our refactored test method <tt>testProjectDaoCanCreateNewProject()</tt>
+is as follows.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+function testProjectDaoCanCreateNewProject()
+{
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $this->setupMockConnectionFor($project);
+ $conn->expectMinimumCallCount('queryForObject', 3);
+ $conn->expectAtLeastOnce('insert');
+ }
+
+ $this->assertProjectCreated($project);
+}
+</com:TTextHighlighter>
+
+<p>To test that the project already exists, we modify the mock
+connection and test for an exception.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+function testProjectExistsException()
+{
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //make the project exist
+ $conn->setReturnValue('queryForObject',
+ $project, array('GetProjectByName', 'Project 1'));
+ $this->setupMockConnectionFor($project);
+ }
+
+ try
+ {
+ $this->assertProjectCreated($project);
+ $this->fail();
+ }
+ catch(TimeTrackerException $e)
+ {
+ $this->pass();
+ }
+}
+</com:TTextHighlighter>
+
+<p>Other test method for testing missing customer and manager users
+are done similarly. At this point, the test case file looks quite large.
+We shall not add more tests to this case and we should rename the file
+from <tt>ProjectDaoTestCase.php</tt> to <tt>CreateNewProjectTestCase.php</tt>.
+
+<div class="note"><b class="tip">Note:</b>
+A heirachical exception class may be more useful in testing for specific
+exceptions. For simplicity, we decided to use only <tt>TimeTrackerException</tt>
+for this small project.
+</div>
+
+<div class="tip"><b class="tip">Tip:</b>Class, method and file naming is very
+important to any project, the name should inform you at first glance what
+is to be expected from the class, method or file. In addition, the naming
+scheme should be uniform over the project.
+</div>
+
+<div class="info"><b class="tip">Comment:</b>
+These test may be too ridget as any changes to the implementation may
+actually cause the tests to fail. This is the case with grey-box/white-box
+testing, you are actually testing the implementation. Black box tests may
+be more preferable, as it should only test the class interface (the method or
+function details such as parameters and may be return values). However,
+with the use of database connection to retrive data within the objects under test,
+it may be more suitable to do intergration tests.
+</div>
+
+</com:TContent>
+
diff --git a/demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page b/demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page
new file mode 100644
index 00000000..f85e00be
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/UserClassAndExceptions.page
@@ -0,0 +1,202 @@
+<com:TContent ID="body">
+<h1>More complete ProjectTestCase</h1>
+<p>
+In creating a new project, we need to check the following:
+<ul>
+ <li>that the project does not already exists, i.e. no duplicate project name</li>
+ <li>that the project creator and project manager exists when a new project is created</li>
+</ul>
+So to perform this task, we will modified the test code.
+<com:TTextHighlighter Language="php" CssClass="source">
+function testProjectDaoCanCreateNewProject()
+{
+ $project = new Project();
+ $project->ID = 1;
+ $project->Name = "Project 1";
+ $project->CreatorUserName = "Customer A";
+ $project->ManagerUserName = "Manager A";
+
+ $customer = new TimeTrackerUser();
+ $customer->ID = 1;
+ $customer->Name = "Customer A";
+
+ $manager = new TimeTrackerUser();
+ $manager->ID = 2;
+ $manager->Name = "Manager A";
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //return the customer and manager
+ $conn->setReturnValue('queryForObject',
+ $customer, array('GetUserByName', 'Customer A'));
+ $conn->setReturnValue('queryForObject',
+ $manager, array('GetUserByName', 'Manager A'));
+
+ //project does not exist
+ $conn->setReturnValue('queryForObject',
+ null, array('GetProjectByName', 'Project 1'));
+
+ $param['project'] = $project;
+ $param['creator'] = $customer->ID;
+ $param['manager'] = $manager->ID;
+
+ $conn->setReturnValue('insert', true,
+ array('CreateNewProject', $param));
+ $conn->setReturnReference('queryForObject',
+ $project, array('GetProjectByID', 1));
+
+ //we expect queryForObject to be called 3 times
+ $conn->expectMinimumCallCount('queryForObject', 3);
+ $conn->expectAtLeastOnce('insert');
+ }
+
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+}
+</com:TTextHighlighter>
+</p>
+<p>It may seem very weird that there is so much code in the tests
+and why we even bother to write all these test codes. Well, using the
+above test code we have the following advantages.</p>
+<div class="info"><b class="tip">Advantages of Mock</b>
+<ol>
+ <li>we don't need a real database base connection to test the code,
+ this means we can start relying on tested code ealier</li>
+ <li>when a test fails we know that problem is not part of the database</li>
+ <li>when a test fail, we can quickly pin point the problem</li>
+ <li>the test suite gives us the confidence to refactor our code</li>
+</ol>
+</div>
+
+<p>Of couse, the test will not be able to cover the higher interactions, such as
+the user interface, so intergration or functional web test will be used later on.
+</p>
+
+<p>So how did we come up with the above tests? We started simple, then we
+ask what sort of things it should handle. We assume that the connection object
+work as expect or known to be unexpected and see how the method we want to test handle
+these situations.</p>
+
+<p>If we run the above test, we will be faced with numerous errors. First will be
+that the <tt>TimeTrackerUser</tt> can not be found.</p>
+
+<h1>Creating a new User Class</h1>
+<p>Notice that the <tt>Project</tt> class contains <tt>CreatorUserName</tt>
+and <tt>ManagerUserName</tt> properties. So at some point we
+are going to need at least one <tt>User</tt> class. We shall name the class as
+<tt>TimeTrackerUser</tt> and save it as <tt>APP_CODE/TimeTrackerUser.php</tt>
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+Prado::using('System.Security.TUser');
+Prado::using('System.Security.TUserManager');
+class TimeTrackerUser extends TUser
+{
+ private $_ID;
+
+ public function __construct()
+ {
+ parent::__construct(new TUserManager());
+ }
+
+ public function getID(){ return $this->_ID; }
+
+ public function setID($value)
+ {
+ if(is_null($this->_ID))
+ $this->_ID = $value;
+ else
+ throw new TimeTrackerUserException(
+ 'timetracker_user_readonly_id');
+ }
+}
+?&gt;
+</com:TTextHighlighter>
+
+<h1>Custom Exceptions</h1>
+<p>We enforce that the ID of the user to be read-only once it has been
+set by throwing a custom exception. Prado's exception classes
+uses a string key to find a localized exception string containing more
+detailed description of the exception. The default exception messages
+are stored in the <tt>framework/Exceptions/messages.txt</tt>. This
+file location can be changed by overriding the <tt>getErrorMessageFile()</tt>
+method of <tt>TException</tt> class. We define a custom exception class
+for all Time Tracker application exceptions as <tt>TimeTrackerException</tt>
+and save the class as <tt>APP_CODE/TimeTrackerException.php</tt>.</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+class TimeTrackerException extends TException
+{
+ /**
+ * @return string path to the error message file
+ */
+ protected function getErrorMessageFile()
+ {
+ return dirname(__FILE__).'/exceptions.txt';
+ }
+}
+?&gt;
+</com:TTextHighlighter>
+
+<p>We then create a <tt>exceptions.txt</tt> file in the <tt>APP_CODE</tt>
+directory with the following content.</p>
+
+<com:TTextHighlighter Language="text" CssClass="source">
+timetracker_user_readonly_id = Time tracker user ID is read-only.
+</com:TTextHighlighter>
+
+<p>Additional parameters passed in the exception constructor can be
+added the message string using <tt>{0}</tt> as the first additional parameter,
+and <tt>{1}</tt> as the second additional parameter, and so on.
+For example, suppose we want to raise the follow exception.
+</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+throw new TimeTrackerException('project_exists', $projectName);
+</com:TTextHighlighter>
+
+<p>The exception error message in <tt>exceptions.txt</tt> may contain something like:</p>
+<com:TTextHighlighter Language="text" CssClass="source">
+project_exists = Time tracker project '{0}' already exists.
+</com:TTextHighlighter>
+
+<h1>Completing the test case</h1>
+<p>From the unit test code, we can pretty much see what the implementation
+for <tt>createNewProject()</tt> will look like.</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+public function createNewProject($project)
+{
+ $sqlmap = $this->getConnection();
+ $creator = $sqlmap->queryForObject('GetUserByName', $project->CreatorUserName);
+ $manager = $sqlmap->queryForObject('GetUserByName', $project->ManagerUserName);
+ $exists = $sqlmap->queryForObject('GetProjectByName', $project->Name);
+ if($exists)
+ {
+ throw new TimeTrackerException(
+ 'project_exists', $project->Name);
+ }
+ else if(!$creator || !$manager)
+ {
+ throw new TimeTrackerException(
+ 'invalid_creator_and_manager',
+ $project->Name, $project->CreatorUserName,
+ $project->ManagerUserName);
+ }
+ else
+ {
+ $param['project'] = $project;
+ $param['creator'] = $creator->ID;
+ $param['manager'] = $manager->ID;
+ return $sqlmap->insert('CreateNewProject', $param);
+ }
+}
+</com:TTextHighlighter>
+
+<div class="tip"><b class="tip">Tip:</b>
+A hierachy of exception class can be used to have fine exception handling.
+Since this is a small project and for simplicity, we shall use the application level
+<tt>TimeTrackerException</tt> exception class for most exception cases.
+</div>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/UsingSQLMap.page b/demos/time-tracker/protected/pages/Docs/UsingSQLMap.page
new file mode 100644
index 00000000..cc2abf4f
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/UsingSQLMap.page
@@ -0,0 +1,210 @@
+<com:TContent ID="body">
+<h1>Using SQLMap Data Mapper</h1>
+<p>Before proceeding with write the code to pass the test in the previous section, we shall
+make a design decision. We shall use SQLMap as our data access layer. Note
+that <b>SQLMap is only available</b> offically in <b>Prado 3.1</b> or later.</p>
+
+<p>SQLMap is an PHP implemenation of an Object/Relational
+data mapper. SQLMap has the following basic features:
+<ol>
+ <li>collects all the Structured Query Language
+(SQL) statements in an external XML file</li>
+ <li>Maps data return from database queries into PHP objects</li>
+ <li>Takes PHP objects as parameters in SQL queries</li>
+</ol>
+<p>SQLMap can be seen as a generic data mapper, rather than an
+Object Relational Mapping (ORM) solution.</p>
+
+<p>The SQLMap API consists of the following methods. See the <a href="sqlmap">SQLMap manual</a>
+for further details.</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+/* Query API */
+public function queryForObject($statementName, $parameter=null, $result=null);
+public function queryForList($statementName, $parameter=null, $result=null,
+ $skip=-1, $max=-1);
+public function queryForPagedList($statementName, $parameter=null, $pageSize=10);
+public function queryForMap($statementName, $parameter=null,
+ $keyProperty=null, $valueProperty=null);
+public function insert($statementName, $parameter=null)
+public function update($statementName, $parameter=null)
+public function delete($statementName, $parameter=null)
+
+/* Connection API */
+public function openConnection()
+public function closeConnection()
+
+/* Transaction API */
+public function beginTransaction()
+public function commitTransaction()
+public function rollBackTransaction()
+</com:TTextHighlighter>
+
+<h2>Fetch and Inserting Data Using SQLMap</h2>
+<p>Back to our <tt>ProjectDao</tt> class, for testing we shall write the code
+assuming we have an SQLMap client instance. The SQLMap client or connection
+will be mocked or faked using a proxy. Later on, we shall extend our tests,
+to become an intergration test, to include
+a test database connection.</p>
+
+<h2>Creating a SQLMap connection</h2>
+<p>We can test the <tt>ProjectDao</tt> using mock objects (see SimpleTest documentation
+regarding mock objects) or using a real database connection. In PHP version
+5.0.x, the sqlite database client is bundled by default. For version 5.1 or later
+the sqlite database client is available as an extension. We shall use a sqlite
+database to conduct our unit tests because the database can be restored to
+the orginal state by just reloading the orginal database file.</p>
+
+<div class="info"><b class="tip">Comment:</b>
+Unit tests using mocked objects may be too ridget as we need to mock
+the connection object and thus the test is of the grey-box/white-box variaity.
+That is, you are actually testing the implementation of the object under test
+consideration. Thus, any changes to the implementation of the objects
+under tests may actually cause the tests to fail. Black box tests may
+be more preferable, as it should only test the class interface (the method or
+function details such as parameters and may be return values). A further
+disadvantage when using mocks for complex database connection is the complexity
+required in setting the mocks for testing.
+</div>
+
+<p>First, let us define the database table for projects.</p>
+<com:TTextHighlighter Language="sql" CssClass="source">
+Field Type Null
+----------------------------------------
+ProjectID INTEGER Yes
+Name VARCHAR(255) No
+Description VARCHAR(255) No
+CreationDate INT No
+Disabled INT(1) No
+EstimateDuration INT No
+CompletionDate INT No
+CreatorID INTEGER No
+ManagerID INTEGER Yes
+</com:TTextHighlighter>
+
+<p>The corresponding SQLite query to create the table is given below.</p>
+
+<com:TTextHighlighter Language="sql" CssClass="source">
+CREATE TABLE projects (
+ ProjectID INTEGER PRIMARY KEY,
+ Name VARCHAR(255) NOT NULL,
+ Description VARCHAR(255) NOT NULL,
+ CreationDate INT NOT NULL,
+ Disabled INT(1) NOT NULL,
+ EstimateDuration INT NOT NULL,
+ CompletionDate INT NOT NULL,
+ CreatorID INTEGER NOT NULL,
+ ManagerID INTEGER
+);
+CREATE UNIQUE INDEX projects_Name ON projects(Name);
+CREATE INDEX project_name_index ON projects(Name);
+</com:TTextHighlighter>
+
+<h2>Testing with Mocked Database Connection</h2>
+<p>At this point, we have said nothing about databases. To create some unit tests
+for the <tt>ProjectDao</tt> class, we are not going to use real database connections
+but using a Mocked <tt>TSqlMapper</tt>. We modifiy the test as follows.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+Prado::using('Application.APP_CODE.*');
+Prado::using('System.DataAccess.SQLMap.TSqlMapper');
+
+Mock::generate('TSqlMapper');
+
+class ProjectDaoTestCase extends UnitTestCase
+{
+ protected $dao;
+ protected $connection;
+
+ function setup()
+ {
+ $this->dao= new ProjectDao();
+ $this->connection = new MockTSqlMapper($this);
+ $this->dao->setConnection($this->connection);
+ }
+
+ function testProjectDaoCanCreateNewProject()
+ {
+ $project = new Project();
+ $project->Name = "Project 1";
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $conn->expectOnce('insert', array('CreateNewProject', $project));
+ $conn->setReturnValue('insert', true);
+
+ $conn->expectOnce('queryForObject', array('GetProjectByID', 1));
+ $conn->setReturnReference('queryForObject', $project);
+ }
+
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+ }
+}
+</com:TTextHighlighter>
+<p>The test code looks slight more complicated because later on we want to
+test the same assertions against some real data.</p>
+<p>
+In the first two lines above, we simply import the business code and the <tt>TSqlMapper</tt> class.
+We generate a <tt>MockTSqlMapper</tt>, a mock class or <tt>TSqlMapper</tt> using
+<tt>Mock::generate('TSqlMapper')</tt>. The method <tt>Mock::generate()</tt> is available from
+the SimpleTest unit testing framework. An instance of <tt>MockTSqlMapper</tt>
+will be used as our test connection for the DAO objects. This allows us to interogate the
+internal workings of our <tt>ProjectDao</tt> class.
+</p>
+
+<p>In the <tt>setup()</tt> (this method is called before every test method), we create an instance of
+<tt>ProjectDao</tt> and set the connection to an instance of <tt>MockTSqlMapper</tt>.
+</p>
+
+<h2>Testing internal workings of ProjectDao</h2>
+
+<p>So how do we test the internal workings of the ProjectDao <tt>createNewProject</tt> method?
+First, we assume that the connection object will perform the correct database insertion and queries.
+Then, we set the return value of the MockTSqlMapper instance to return what we have assumed.
+In addition, we expect that the TSqlMapper method <tt>queryForObject</tt> is called only once with
+the parameters we have assumed (e.g. <tt>$project</tt>). See the SimpleTest tutorial for further details regarding
+unit testing with Mocks.
+</p>
+
+<p>In our assertions and expectations, we have
+<com:TTextHighlighter Language="php" CssClass="source">
+$conn->expectOnce('insert', array('CreateNewProject', $project));
+$conn->setReturnValue('insert', true);
+
+$this->assertTrue($this->dao->createNewProject($project));
+</com:TTextHighlighter>
+This means that, we expect the <tt>createNewProject</tt> method in <tt>ProjectDao</tt>
+to call the TSqlMapper method <tt>insert</tt> with parameter <tt>$project</tt> only once.
+In addition, we <b>assume</b> that the returned value from the <tt>insert</tt> method of TSqlMapper
+returns <tt>true</tt>. Finally, we test the <tt>createNewProject</tt> method with an assertion.
+</p>
+
+<p>We now run the unit tests, we see that there are some failures or errors. Since we
+have not created any code in <tt>ProjectDao</tt> that performs what we want, the tests will fail.
+So lets make these test pass, we add some code to <tt>ProjectDao</tt> class.
+
+<com:TTextHighlighter Language="php" CssClass="source">
+class ProjectDao extends BaseDao
+{
+ public function createNewProject($project)
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->insert('CreateNewProject', $project);
+ }
+
+ public function getProjectByID($projectID)
+ {
+ $sqlmap = $this->getConnection();
+ return $sqlmap->queryForObject('GetProjectByID', $projectID);
+ }
+}
+</com:TTextHighlighter>
+</p>
+
+<p>If we run the unit tests again, we should see a green bar indicating that the tests have passed.
+We can now proceed further and add more tests. Of course, the above test does not cover many
+conditions, such as, <i>what happens if the project already exists?</i>, the details of these
+tests is the subject of the next section. The full test suite can be found in the source.
+</p>
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page b/demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page
new file mode 100644
index 00000000..b4da0952
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/WritingFunctionalTest.page
@@ -0,0 +1,39 @@
+<com:TContent ID="body">
+<h1>Writing a Functional Web Test</h1>
+<p>In addition to unit testing, we shall also do some functional
+testing or web testing. Functional tests are, in this case, basically automated tests that will
+interact with the overall web application, as if it was the user, while checking the output for correctness.
+The functional test tool we shall use here is based on <a href="http://selenium.openqa.org/">Selenium</a> where the test cases can be written and run using PHP and SimpleTest.
+</p>
+<com:TTextHighlighter>
+&lt;?php
+class HelloPradoTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open('../index.php');
+ $this->assertTextPresent('Welcome to Prado!');
+ }
+}
+?&gt;
+</com:TTextHighlighter>
+<p>Save the code as <tt>HelloPradoTestCase.php</tt> in the <tt>document_root/time-tracker/tests/functional/</tt>
+directory.</p>
+
+<p>
+Functional test cases are written very similar to unit test cases. The method such as
+<tt>open($url)</tt> are those found in Selenium. All the methods available in Selenium are available.
+</p>
+
+<h2>Run your first unit test case from your browser</h2>
+<p>Point your browser to your development server's unit test case runner, e.g.
+ <tt>http://web-server-address/time-tracker/tests/functional.php</tt>. You should see the following
+ <img src="<%~ functional_test1.png %>" class="figure"/>
+ <div class="caption"><b>Figure 4:</b> Functional test runner</div>
+</p>
+<p>Clicking on the <b>All</b> button, you should see
+ <img src="<%~ functional_test2.png %>" class="figure"/>
+ <div class="caption"><b>Figure 5:</b> Functional test success</div>
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page b/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page
new file mode 100644
index 00000000..32c7bc79
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/WritingUnitTest.page
@@ -0,0 +1,73 @@
+<com:TContent ID="body">
+<h1>Writing a Unit Test</h1>
+<p>Unit testing is a useful tool when we want to start to test
+ our individual business logic classes.
+ The <tt>tests/unit</tt> directory will be used to hold the unit test cases and <tt>tests/functional</tt> directory
+to hold the function test cases.
+</p>
+
+<h2>Write a unit test case</h2>
+<p>We will start be writing a very simple unit test case.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+class ProjectTestCase extends UnitTestCase
+{
+ function testProjectClassExists()
+ {
+ $project = new Project();
+ $this->pass();
+ }
+}
+?&gt;
+</com:TTextHighlighter>
+<p>Save the code as <tt>ProjectTestCase.php</tt> in the <tt>document_root/time-tracker/tests/unit/</tt>
+directory.</p>
+
+<h2>Run your first unit test case from your browser</h2>
+<p>Point your browser to your development server's unit test case runner, e.g.
+ <tt>http://web-server-address/time-tracker/tests/unit.php</tt>. You should see the following
+<img src="<%~ unit_test1.png %>" class="figure"/>
+<div class="caption"><b>Figure 1:</b> Unit test runner</div>
+</p>
+<p>Clicking on the <tt>ProjectTestCase.php</tt> like, you should see
+<img src="<%~ unit_test2.png %>" class="figure"/>
+<div class="caption"><b>Figure 2:</b> Unit test failure</div>
+</p>
+
+<h2>Smallest step to make the test pass.</h2>
+
+<p>Obviously, we need create the class <tt>Project</tt>, so lets define the class.</p>
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+class Project
+{
+}
+?&gt;
+</com:TTextHighlighter>
+<p>Save the above code as <tt>time-tracker/protected/pages/APP_CODE/Project.php</tt>.
+ Where the <tt>APP_CODE</tt> directory will contain most of your business logic code for the Time Tracker application.</p>
+<p>We also need to add the following line in our test case so as to include the <tt>Project</tt> class file when running the tests.</p>
+
+<p class="note">
+The statement <tt>Prado::using('Application.APP_CODE.Project')</tt> basically
+loads the <tt>Project.php</tt> class file. It assumes that a class name <tt>Project</tt> has filename <tt>Project.php</tt>.
+For futher details regarding <tt>Prado::using</tt> can be found in <a href="http://www.pradosoft.com/demos/quickstart/index.php?page=Fundamentals.Components#704">Prado Namespaces</a> documentation.
+</p>
+
+<com:TTextHighlighter Language="php" CssClass="source">
+&lt;?php
+Prado::using('Application.APP_CODE.Project');
+class ProjectTestCase extends UnitTestCase
+{
+ ...
+}
+?&gt;
+</com:TTextHighlighter>
+<p>Run the unit test runner again, we see that the test has passed.
+ <img src="<%~ unit_test3.png %>" class="figure"/>
+ <div class="caption"><b>Figure 3:</b> Unit test success</div>
+</p>
+<p>
+Later on, we shall write more test cases. See the SimpleTest documentation for detailed tutorial on writing test cases.</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/config.xml b/demos/time-tracker/protected/pages/Docs/config.xml
new file mode 100644
index 00000000..e0850c2c
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/config.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<configuration>
+ <paths>
+ <alias id="Pages" path="." />
+ </paths>
+ <pages MasterClass="Application.controls.Layout" Theme="PradoSoft" />
+</configuration> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/db.png b/demos/time-tracker/protected/pages/Docs/db.png
new file mode 100644
index 00000000..f2209ef4
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/db.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/functional_test1.png b/demos/time-tracker/protected/pages/Docs/functional_test1.png
new file mode 100644
index 00000000..33908734
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/functional_test1.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/functional_test2.png b/demos/time-tracker/protected/pages/Docs/functional_test2.png
new file mode 100644
index 00000000..fb507e72
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/functional_test2.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/preface.page b/demos/time-tracker/protected/pages/Docs/preface.page
new file mode 100644
index 00000000..fa0ba5c3
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/preface.page
@@ -0,0 +1,19 @@
+<com:TContent ID="body">
+<h1>Prado Time Tracker</h1>
+<p>This documentation is complete walk-through guide detailing the
+steps involved in implementating of the Prado Time Tracker web application.
+</p>
+<h2>Target Audience</h2>
+<p>The guide is intended for readers who has some experience
+using PHP version 5 and some basic concepts of Object-Oriented application
+design. The guide will assume that the reader understands the primary concepts
+of class, methods, class inheritance, and other basic features offered by PHP
+version 5 or later.</p>
+<h2>Questions and Comments</h2>
+<p>Readers are encouraged to ask questions as the forum regarding any
+particular aspect of this documentation. Comment and constructive criticisms
+are most welcome. Questions and comments may be directed at
+<a href="http://www.pradosoft.com/forum/">http://www.pradosoft.com/forum/</a>.
+</p>
+
+</com:TContent> \ No newline at end of file
diff --git a/demos/time-tracker/protected/pages/Docs/project1.png b/demos/time-tracker/protected/pages/Docs/project1.png
new file mode 100644
index 00000000..a250a943
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/project1.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/unit_test1.png b/demos/time-tracker/protected/pages/Docs/unit_test1.png
new file mode 100644
index 00000000..66b62e19
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/unit_test1.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/unit_test2.png b/demos/time-tracker/protected/pages/Docs/unit_test2.png
new file mode 100644
index 00000000..e33544d2
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/unit_test2.png
Binary files differ
diff --git a/demos/time-tracker/protected/pages/Docs/unit_test3.png b/demos/time-tracker/protected/pages/Docs/unit_test3.png
new file mode 100644
index 00000000..bbc04551
--- /dev/null
+++ b/demos/time-tracker/protected/pages/Docs/unit_test3.png
Binary files differ
diff --git a/demos/time-tracker/tests/functional.php b/demos/time-tracker/tests/functional.php
new file mode 100644
index 00000000..c216ada8
--- /dev/null
+++ b/demos/time-tracker/tests/functional.php
@@ -0,0 +1,10 @@
+<?php
+
+include_once '../../prado-trunk\tests\test_tools\functional_tests.php';
+
+$test_cases = dirname(__FILE__)."/functional";
+
+$tester=new PradoFunctionalTester($test_cases);
+$tester->run(new SimpleReporter());
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/functional/HelloPradoTestCase.php b/demos/time-tracker/tests/functional/HelloPradoTestCase.php
new file mode 100644
index 00000000..b9e4f1e3
--- /dev/null
+++ b/demos/time-tracker/tests/functional/HelloPradoTestCase.php
@@ -0,0 +1,14 @@
+<?php
+
+//web testing
+class HelloPradoTestCase extends SeleniumTestCase
+{
+ function testIndexPage()
+ {
+ $this->open('../index.php');
+ $this->assertTextPresent('Welcome to Prado!');
+ //add more test assertions...
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit.php b/demos/time-tracker/tests/unit.php
new file mode 100644
index 00000000..a920b205
--- /dev/null
+++ b/demos/time-tracker/tests/unit.php
@@ -0,0 +1,11 @@
+<?php
+
+include_once '../../prado-trunk/tests/test_tools/unit_tests.php';
+
+$app_directory = "../protected";
+$test_cases = dirname(__FILE__)."/unit";
+
+$tester = new PradoUnitTester($test_cases, $app_directory);
+$tester->run(new HtmlReporter());
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/AddUserToProjectTestCase.php b/demos/time-tracker/tests/unit/AddUserToProjectTestCase.php
new file mode 100644
index 00000000..36defc57
--- /dev/null
+++ b/demos/time-tracker/tests/unit/AddUserToProjectTestCase.php
@@ -0,0 +1,35 @@
+<?php
+
+require_once(dirname(__FILE__).'/ProjectDaoTestCase.php');
+
+class AddUserToProjectTestCase extends ProjectDaoTestCase
+{
+ function testCanAddNewUserToProject()
+ {
+ $project = $this->createNewTestProject();
+
+ $user = new TimeTrackerUser();
+ $user->ID = 3;
+ $user->Name = "test user 1";
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $this->setupMockConnectionFor($project);
+ $conn->setReturnReference('queryForObject', $user, array('GetUserByName', $user->Name));
+ $conn->setReturnValue('queryForList', array(), array('GetProjectMembers', $project));
+
+ $param['project'] = $project;
+ $param['user'] = $user;
+
+ $conn->setReturnValue('insert', true, array('AddNewUserToProject', $param));
+
+ $conn->expectAtLeastOnce('insert');
+ $conn->expectAtLeastOnce('queryForList');
+ }
+
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertTrue($this->dao->addUserToProject($project, $user));
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/CreateNewProjectTestCase.php b/demos/time-tracker/tests/unit/CreateNewProjectTestCase.php
new file mode 100644
index 00000000..0aa67405
--- /dev/null
+++ b/demos/time-tracker/tests/unit/CreateNewProjectTestCase.php
@@ -0,0 +1,90 @@
+<?php
+
+require_once(dirname(__FILE__).'/ProjectDaoTestCase.php');
+
+class CreateNewProjectTestCase extends ProjectDaoTestCase
+{
+ function testProjectDaoCanCreateNewProject()
+ {
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $this->setupMockConnectionFor($project);
+ $conn->expectMinimumCallCount('queryForObject', 3);
+ $conn->expectAtLeastOnce('insert');
+ }
+
+ $this->assertProjectCreated($project);
+ }
+
+ function testProjectExistsException()
+ {
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //make the project exist
+ $conn->setReturnValue('queryForObject',
+ $project, array('GetProjectByName', 'Project 1'));
+ $this->setupMockConnectionFor($project);
+ }
+
+ try
+ {
+ $this->assertProjectCreated($project);
+ $this->fail();
+ }
+ catch(TimeTrackerException $e)
+ {
+ $this->pass();
+ }
+ }
+ function testProjectCustomerNotExistsException()
+ {
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //customer does not exist
+ $conn->setReturnValue('queryForObject',
+ null, array('GetUserByName', 'Customer A'));
+ $this->setupMockConnectionFor($project);
+ }
+
+ try
+ {
+ $this->assertProjectCreated($project);
+ $this->fail();
+ }
+ catch(TimeTrackerException $e)
+ {
+ $this->pass();
+ }
+ }
+
+ function testProjectManagerNotExistsException()
+ {
+ $project = $this->createNewTestProject();
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ //manager does not exist
+ $conn->setReturnValue('queryForObject',
+ null, array('GetUserByName', 'Manager A'));
+ $this->setupMockConnectionFor($project);
+ }
+
+ try
+ {
+ $this->assertProjectCreated($project);
+ $this->fail();
+ }
+ catch(TimeTrackerException $e)
+ {
+ $this->pass();
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/ProjectDaoTestCase.php b/demos/time-tracker/tests/unit/ProjectDaoTestCase.php
new file mode 100644
index 00000000..bc576630
--- /dev/null
+++ b/demos/time-tracker/tests/unit/ProjectDaoTestCase.php
@@ -0,0 +1,92 @@
+<?php
+
+//formerly PradoDaoTestCase.php
+
+Prado::using('Application.APP_CODE.*');
+Prado::using('System.DataAccess.SQLMap.TSqlMapper');
+
+Mock::generate('TSqlMapper');
+
+class ProjectDaoTestCase extends UnitTestCase
+{
+ protected $dao;
+ protected $connection;
+
+ function setup()
+ {
+ $this->dao= new ProjectDao();
+ $this->connection = new MockTSqlMapper($this);
+ $this->dao->setConnection($this->connection);
+ }
+
+/*
+ //Simple test case, will not detect project existanc
+ //This case will clash with the more complete test case below.
+ function testProjectDaoCanCreateNewProject()
+ {
+ $project = new Project();
+ $project->Name = "Project 1";
+
+ if(($conn = $this->connection) instanceof MockTSqlMapper)
+ {
+ $conn->expectOnce('insert', array('CreateNewProject', $project));
+ $conn->setReturnValue('insert', true);
+
+ $conn->expectOnce('queryForObject', array('GetProjectByID', 1));
+ $conn->setReturnReference('queryForObject', $project);
+ }
+
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+ }
+*/
+ function setupMockConnectionFor($project)
+ {
+ $customer = new TimeTrackerUser();
+ $customer->ID = 1;
+ $customer->Name = "Customer A";
+
+ $manager = new TimeTrackerUser();
+ $manager->ID = 2;
+ $manager->Name = "Manager A";
+
+ $conn = $this->connection;
+
+ //return the customer and manager
+ $conn->setReturnValue('queryForObject',
+ $customer, array('GetUserByName', 'Customer A'));
+ $conn->setReturnValue('queryForObject',
+ $manager, array('GetUserByName', 'Manager A'));
+
+ //project does not exist
+ $conn->setReturnValue('queryForObject',
+ null, array('GetProjectByName', 'Project 1'));
+
+ $param['project'] = $project;
+ $param['creator'] = $customer->ID;
+ $param['manager'] = $manager->ID;
+
+ $conn->setReturnValue('insert',
+ true, array('CreateNewProject', $param));
+ $conn->setReturnReference('queryForObject',
+ $project, array('GetProjectByID', 1));
+ }
+
+ function createNewTestProject()
+ {
+ $project = new Project();
+ $project->Name = "Project 1";
+ $project->CreatorUserName = "Customer A";
+ $project->ManagerUserName = "Manager A";
+
+ return $project;
+ }
+
+ function assertProjectCreated($project)
+ {
+ $this->assertTrue($this->dao->createNewProject($project));
+ $this->assertEqual($this->dao->getProjectByID(1), $project);
+ }
+
+}
+?> \ No newline at end of file
diff --git a/demos/time-tracker/tests/unit/ProjectTestCase.php b/demos/time-tracker/tests/unit/ProjectTestCase.php
new file mode 100644
index 00000000..a61c4f77
--- /dev/null
+++ b/demos/time-tracker/tests/unit/ProjectTestCase.php
@@ -0,0 +1,15 @@
+<?php
+
+//import Project class.
+Prado::using('Application.APP_CODE.Project');
+
+class ProjectTestCase extends UnitTestCase
+{
+ function testProjectClassExists()
+ {
+ $project = new Project();
+ $this->pass();
+ }
+}
+
+?> \ No newline at end of file