UNPKG

can

Version:

MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.

1,739 lines (1,310 loc) 157 kB
<!DOCTYPE html> <!--#################################################################### THIS IS A GENERATED FILE -- ANY CHANGES MADE WILL BE OVERWRITTEN INSTEAD CHANGE: source: [object Object] @page guides/atm ######################################################################## --> <html lang="en"> <head> <meta charset="utf-8"> <title>CanJS - ATM Guide</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <link rel="stylesheet" type="text/css" href="../static/bundles/bit-docs-site/static.css"> <link rel="shortcut icon" sizes="16x16 24x24 32x32 48x48 64x64" href="/docs/images/canjs_favicon.ico"> <link rel="apple-touch-icon" sizes="57x57" href="../../docs/images/canjs_favicon_57x57.png"> <link rel="apple-touch-icon-precomposed" sizes="57x57" href="../../docs/images/canjs_favicon_57x57.png"> <link rel="apple-touch-icon" sizes="72x72" href="../../docs/images/canjs_favicon_72x72.png"> <link rel="apple-touch-icon" sizes="114x114" href="../../docs/images/canjs_favicon_114x114.png"> <link rel="apple-touch-icon" sizes="120x120" href="../../docs/images/canjs_favicon_128x128.png"> <link rel="apple-touch-icon" sizes="144x144" href="../../docs/images/canjs_favicon_144x144.png"> <link rel="apple-touch-icon" sizes="152x152" href="../../docs/images/canjs_favicon_152x152.png"> <meta content="yes" name="apple-mobile-web-app-capable"> <meta name="apple-mobile-web-app-status-bar-style" content="white-translucent"> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-2302003-11', 'auto'); ga('send', 'pageview'); </script> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger"/> <label for="nav-trigger">Menu</label> <div id="everything"> <div id="left" class="column"> <div class="top-left"> <div class="brand"> <div class="logo"> <a href="../../index.html" alt="CanJS"></a> <div class="dropdown project-dropdown"> <a href="https://donejs.com/">DoneJS</a> <a href="http://stealjs.com/">StealJS</a> <a href="http://jquerypp.com/">jQuery ++</a> <a href="https://funcunit.com/">FuncUnit</a> <a href="http://documentjs.com/">DocumentJS</a> </div> </div> <div class="version"> <div class="version-number"> 3.0.0 </div> <div class="dropdown version-dropdown"> <a href="https://v2.canjs.com">2.3.27</a> </div> </div> </div> <div class="search-bar"> <p> &nbsp; </p> </div> </div> <div class="bottom-left"> <div class="social-side-container"> <ul class="social-side"> <li> <a class="header-mobile github" href="https://github.com/canjs/canjs" target="_blank"><img class="social-icon-small" src="../../docs/images/github.png">Github</a> </li> <li> <a class="header-mobile twitter" href="https://twitter.com/canjs" target="_blank"><img class="social-icon-small" src="../../docs/images/twitter.png">Twitter</a> </li> </ul> <ul class="social-side"> <li> <a class="header-mobile" href="https://gitter.im/canjs/canjs" target="_blank">Chat</a> </li> <li> <a class="header-mobile" href="http://forums.donejs.com/c/canjs" target="_blank">Forum</a> </li> </ul> </div> <ul> <li class=" parent expanded"> <a class="page" href="../guides.html" title="Welcome to CanJS! These guides are here to help you develop and improve your relationship with CanJS. After all, picking a JavaScript framework is a commitment. We want CanJS to be the framework you marry. This page helps you know how advance through the different stages of this relationship:"> Guides </a> <ul> <li> <span>introduction</span> <ul> <li class=" "> <a class="page" href="mission.html" title="Learn about CanJS&#x27;s mission, why it matters, and how we&#x27;ve worked (and will keep working) to accomplish it."> Mission </a> </li> <li class=" "> <a class="page" href="technical.html" title=""> Technical Highlights </a> </li> <li class=" "> <a class="page" href="who-uses-canjs.html" title=""> Who uses CanJS? </a> </li> </ul> </li> <li> <span>experiment</span> <ul> <li class=" "> <a class="page" href="chat.html" title="This guide walks through building real time chat application with CanJS&#x27;s Core libraries. It takes about 30 minutes to complete."> Chat Guide </a> </li> <li class=" "> <a class="page" href="todomvc.html" title="This guide walks through building a slightly modified version of TodoMVC with CanJS&#x27;s Core libraries and can-fixture. It takes about 1 hour to complete."> TodoMVC Guide </a> </li> <li class="current parent expanded"> <a class="page" href="atm.html" title="This guide walks through building and testing an ATM application with CanJS&#x27;s Core libraries. It teaches how to do test driven development (TDD) and manage complex state. It takes about 2 hours to complete."> ATM Guide </a> </li> <li class=" "> <a class="page" href="setup.html" title="CanJS is packaged in multiple ways so that it can fit into any development workflow. Learn how to setup CanJS in different environments."> Setting up CanJS </a> </li> </ul> </li> <li> <span>commitment</span> <ul> <li class=" "> <a class="page" href="api.html" title="This page walks through how to use and understand CanJS&#x27;s API documentation."> API Guide </a> </li> <li class=" "> <a class="page" href="examples.html" title=""> Examples </a> </li> <li class=" "> <a class="page" href="../roadmap.html" title="Learn about CanJS&#x27;s future plans and how we make them, and how you can influence them."> Roadmap </a> </li> <li class=" "> <a class="page" href="../migrate-3.html" title=""> Migrating to 3.0 </a> </li> </ul> </li> <li> <span>contribute</span> <ul> <li class=" "> <a class="page" href="contributing/bug-report.html" title="Learn how to submit a bug report."> Bug Report </a> </li> <li class=" "> <a class="page" href="contributing/code.html" title="Learn how contribute a code change to CanJS."> Code </a> </li> <li class=" "> <a class="page" href="contributing/documentation.html" title="Learn how to improve CanJS&#x27;s site and documentation."> Documentation </a> </li> <li class=" "> <a class="page" href="contributing/evangelism.html" title="Learn about resources that can help you spread the word about CanJS."> Evangelism </a> </li> <li class=" "> <a class="page" href="contributing/feature-suggestion.html" title="Learn how to suggest a feature."> Feature Suggestion </a> </li> <li class=" "> <a class="page" href="contributing/releases.html" title="Release and hosting information for CanJS maintainers."> Releases </a> </li> </ul> </li> </ul> </li> <li class=" "> <a class="page" href="../can-core.html" title="The best, most hardened and generally useful libraries in CanJS."> Core </a> </li> <li class=" "> <a class="page" href="../can-ecosystem.html" title="Useful libraries that extend or add important features to the core collection."> Ecosystem </a> </li> <li class=" "> <a class="page" href="../can-infrastructure.html" title="Utility libraries that power the core and ecosystem collection."> Infrastructure </a> </li> <li class=" "> <a class="page" href="../can-legacy.html" title="Former libraries that we still accept patches for, but are not under active development."> Legacy </a> </li> </ul> </div> </div> <div id="right" class="column"> <div class="top-right"> <div class="top-right-top"> <ul class="top-right-bitovi"> <li class="dropdown"> <a href="http://bitovi.com" class="bitovi icon-bits">Bitovi</a> <ul class="dropdown-menu"> <li><a href="http://bitovi.com">Bitovi.com</a></li> <li><a href="http://bitovi.com/blog/">Blog</a></li> <li><a href="http://bitovi.com/consulting/">Consulting</a></li> <li><a href="http://bitovi.com/training/">Training</a></li> <li><a href="http://bitovi.com/open-source/">Open Source</a></li> </ul> </li> </ul> <div class="brand"> <div class="logo"> <a href="../../index.html" alt="CanJS"></a> </div> </div> <ul class="top-right-links"> <li> <a href="https://gitter.im/canjs/canjs">Chat</a> </li> <li> <a href="http://forums.donejs.com/c/canjs">Forum</a> </li> <li> <a class="github-button nav-social" href="https://github.com/canjs/canjs" data-count-href="/canjs/canjs/stargazers" data-count-api="/repos/canjs/canjs#stargazers_count">Star</a> </li> <li> <a href="https://twitter.com/canjs" class="twitter-follow-button nav-social" data-show-count="true" data-show-screen-name="false">Follow @canjs</a><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script> </li> </ul> </div> <div class="breadcrumb"> <li><a href="../../index.html">CanJS</a></li> / <li><a href="../guides.html">Guides</a></li> / <li><a href="atm.html">ATM Guide</a></li> <li class="breadcrumb-dropdown">/ <a> On this page</a> <ul class="on-this-page"></ul> </li> <div class="nav-toggle" title="Back to top"></div> </div> </div> <div class="bottom-right"> <article> <section class="title"> <div class="page-type"> <h1>ATM Guide</h1> <div>page</div> </div> <section class="description"> <p>This guide walks through building and <strong>testing</strong> an ATM application with CanJS's <a href="../can-core.html" title="The best, most hardened and generally useful libraries in CanJS.">Core libraries</a>. It teaches how to do test driven development (TDD) and manage complex state. It takes about 2 hours to complete.</p> </section> </section> <section class="on-this-page-table"> </section> <section class="title-footer"> <ul class="title-links"> <!-- <li><a href="#">docco</a></li> --> <li><a href="//github.com/canjs/canjs/tree/v3.0.0/docs/can-guides/experiment/atm/atm.md">source</a></li> <!-- <li><a href="#">download</a></li> --> <!-- <li><a href="#">tests</a></li> --> </ul> </section> <section class="body"> <h2>Overview</h2> <p>Checkout the final app:</p> <p><a class="jsbin-embed" href="http://jsbin.com/yayupo/10/embed?js,output">JS Bin on jsbin.com</a></p> <p>Notice it has tests at the bottom of the <code>Output</code> tab.</p> <p>Watch the following video to see it in action:</p> <blockquote> <p>TODO: VIDEO OF IT IN ACTION</p> </blockquote> <h2>Setup</h2> <p>The easiest way to get started is to clone the following JSBin by clicking the <strong>JS Bin</strong> button on the top left:</p> <p><a class="jsbin-embed" href="http://justinbmeyer.jsbin.com/meziyu/3/edit?html,js,output">JS Bin on jsbin.com</a></p> <p>The JSBin is designed to run both the application and its tests in the <code>OUTPUT</code> tab. To set this up, the <code>HTML</code> tab:</p> <ul> <li><p>Loads QUnit for its testing library. It also includes the <code>&lt;div id=&quot;qunit&quot;&gt;&lt;/div&gt;</code> element where QUnit's test results will be written to.</p></li> <li><p>Loads <a href="https://unpkg.com/can/dist/global/can.all.js">can.all.js</a>, which is a script that includes all of CanJS core under a single global <code>can</code> namespace.</p> <p>Generally speaking, you should not use the global can script and instead should import things directly with a module loader like <a href="http://stealjs.com">StealJS</a>, WebPack or Browserify. Read <a href="setup.html" title="CanJS is packaged in multiple ways so that it can fit into any development workflow. Learn how to setup CanJS in different environments.">Setting up CanJS</a> on how to setup CanJS in a real app.</p></li> <li><p>Includes the content for a <code>app-template</code> <a href="../can-stache.html" title="Live binding Mustache and Handlebars-compatible templates.">can-stache</a> template. This template provides the title for the ATM app, and uses the <code>&lt;atm-machine&gt;</code> custom <a href="../can-component.html" title="Create a custom element that can be used to manage widgets or application logic.">can-component</a> element that will eventually provide the ATM functionality.</p></li> </ul> <p>The <code>JS</code> tab is split into two sections:</p> <ul> <li><code>CODE</code> - The ATM's models, view-models and component code will go here.</li> <li><code>TESTS</code> - The ATM's tests will go here.</li> </ul> <p>Normally, your application's code and test will be in separate files and loaded by different html pages. But we combine them here to fit within JSBin's limitations.</p> <p>The <code>CODE</code> section is rendering the <code>app-template</code> with:</p> <pre><code class="language-js">document.body.insertBefore( can.stache.from(&quot;app-template&quot;)({}), document.body.firstChild ); </code></pre> <p>The <code>TESTS</code> section is labeling which module will be tested:</p> <pre><code class="language-js">QUnit.module(&quot;ATM system&quot;, {}); </code></pre> <h2>Mock out switching between pages</h2> <p>In this section, we will mock out which pages will be shown as the <code>state</code> of the <code>ATM</code> changes.</p> <p>Update the <code>HTML</code> tab to:</p> <ul> <li>Switch between different pages of the application as the <code>ATM</code> view-model's <code>state</code> property changes with <a href="../can-stache.helpers.switch.html" title="">{{#switch expression}}</a>.</li> </ul> <pre><code class="language-html">&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta name=&quot;description&quot; content=&quot;CanJS 3.0 - ATM Guide - Setup&quot;&gt; &lt;meta charset=&quot;utf-8&quot;&gt; &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot;&gt; &lt;title&gt;JS Bin&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;script type='text/stache' id='atm-template'&gt; &lt;div class=&quot;screen&quot;&gt; &lt;div class=&quot;screen-content&quot;&gt; &lt;div class=&quot;screen-glass&quot;&gt; {{#switch state}} {{#case &quot;readingCard&quot;}} &lt;h2&gt;Reading Card&lt;/h2&gt; {{/case}} {{#case &quot;readingPin&quot;}} &lt;h2&gt;Reading Pin&lt;/h2&gt; {{/case}} {{#case &quot;choosingTransaction&quot;}} &lt;h2&gt;Choose Transaction&lt;/h2&gt; {{/case}} {{#case &quot;pickingAccount&quot;}} &lt;h2&gt;Pick Account&lt;/h2&gt; {{/case}} {{#case &quot;depositInfo&quot;}} &lt;h2&gt;Deposit&lt;/h2&gt; {{/case}} {{#case &quot;withdrawalInfo&quot;}} &lt;h2&gt;Withdraw&lt;/h2&gt; {{/case}} {{#case &quot;successfulTransaction&quot;}} &lt;h2&gt;Transaction Successful!&lt;/h2&gt; {{/case}} {{#case &quot;printingReceipt&quot;}} &lt;h2&gt;Printing Receipt&lt;/h2&gt; {{/case}} {{#default}} &lt;h2&gt;Error&lt;/h2&gt; &lt;p&gt;Invalid state - {{state}}&lt;/p&gt; {{/default}} {{/switch}} &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/script&gt; &lt;script type='text/stache' id='app-template'&gt; &lt;div class=&quot;title&quot;&gt; &lt;h1&gt;canATM&lt;/h1&gt; &lt;/div&gt; &lt;atm-machine/&gt; &lt;/script&gt; &lt;script src=&quot;https://code.jquery.com/jquery-2.2.4.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://unpkg.com/can/dist/global/can.all.js&quot;&gt;&lt;/script&gt; &lt;div id=&quot;qunit&quot;&gt;&lt;/div&gt; &lt;link rel=&quot;stylesheet&quot; href=&quot;http://code.jquery.com/qunit/qunit-1.12.0.css&quot;&gt; &lt;script src=&quot;http://code.jquery.com/qunit/qunit-1.12.0.js&quot;&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p><span line-highlight='12-60,only'></span> Update the <code>JavaScript</code> tab to:</p> <ul> <li>Create the <code>ATM</code> view-model with a <code>state</code> property initialized to <code>readingCard</code> with <a href="../can-define/map/map.html" title="Create observable objects.">can-define/map/map</a>.</li> <li>Create an <code>&lt;atm-machine&gt;</code> custom element with <a href="../can-component.html" title="Create a custom element that can be used to manage widgets or application logic.">can-component</a>.</li> </ul> <pre><code class="language-js">// ======================================== // CODE // ======================================== var ATM = can.DefineMap.extend({ state: {type: &quot;string&quot;, value: &quot;readingCard&quot;} }); can.Component.extend({ tag: &quot;atm-machine&quot;, view: can.stache.from(&quot;atm-template&quot;), ViewModel: ATM, }); document.body.insertBefore( can.stache.from(&quot;app-template&quot;)({}), document.body.firstChild ); // ======================================== // TESTS // ======================================== QUnit.module(&quot;ATM system&quot;, {}); </code></pre> <p><span line-highlight='5-13,only'></span> When complete, you should see the <strong>&quot;Reading Card&quot;</strong> title.</p> <p>This step outlines the page transitions we're going to make the <code>state</code> property transition between:</p> <ul> <li>readingCard</li> <li>readingPin</li> <li>choosingTransaction</li> <li>pickingAccount</li> <li>depositInfo</li> <li>withdrawalInfo</li> <li>successfulTransaction</li> <li>printingReceipt</li> </ul> <p>Each of those states are present in the following state diagram.</p> <p><img src="../../docs/can-guides/experiment/atm/1-pages-template/state-diagram.png"></p> <p>We'll build out these pages once we build the <code>Card</code> and <code>Transaction</code> sub-models that will make building the ATM view model easier.</p> <h2>Card tests</h2> <p>In this section, we will:</p> <ul> <li>Design an API for an ATM <code>Card</code></li> <li>Write out tests for the card.</li> </ul> <p>An ATM <code>Card</code> will take a card <code>number</code> and <code>pin</code>. It will start out as having a <code>state</code> of <code>&quot;unverified&quot;</code>. It will have a <code>verify</code> method that will change the <code>state</code> to <code>&quot;verifying&quot;</code> and if the response is successful, <code>state</code> will change to <code>&quot;verified&quot;</code>.</p> <p>Update the <code>JS</code> tab to:</p> <ul> <li>Make the fake data request delay <code>1ms</code> by setting <a href="../can-fixture.delay.html" title="can-fixture.delay">delay</a> to <code>1</code> before every test and restoring it to <code>2s</code> after every test runs.</li> <li>Write a test that creates a valid card, calls <code>.verify()</code>, and asserts the <code>state</code> is <code>&quot;verified&quot;</code>.</li> <li>Write a test that creates a invalid card, calls <code>.verify()</code>, and asserts the <code>state</code> is <code>&quot;invalid&quot;</code>.</li> </ul> <pre><code class="language-js">// ======================================== // CODE // ======================================== var ATM = can.DefineMap.extend({ state: {type: &quot;string&quot;, value: &quot;readingCard&quot;} }); can.Component.extend({ tag: &quot;atm-machine&quot;, view: can.stache.from(&quot;atm-template&quot;), ViewModel: ATM, }); document.body.insertBefore( can.stache.from(&quot;app-template&quot;)({}), document.body.firstChild ); // ======================================== // TESTS // ======================================== QUnit.module(&quot;ATM system&quot;, { setup: function() { can.fixture.delay = 1; }, teardown: function() { can.fixture.delay = 2000; } }); QUnit.asyncTest(&quot;Valid Card&quot;, function() { var c = new Card({ number: &quot;01234567890&quot;, pin: 1234 }); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;verified&quot;, &quot;card is verified&quot;); QUnit.start(); }); }); QUnit.asyncTest(&quot;Invalid Card&quot;, function() { var c = new Card({}); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;invalid&quot;, &quot;card is invalid&quot;); QUnit.start(); }); }); </code></pre> <p><span line-highlight='24-70,only'></span> When complete you should have a breaking test. Now lets make it pass.</p> <h2>Card model</h2> <p>In this section, we will:</p> <ul> <li>Implement the <code>Card</code> model so that all tests pass.</li> </ul> <p>Update the <code>JavaScript</code> tab to:</p> <ul> <li>Simulate the <code>/verifyCard</code> with <a href="../can-fixture.html" title="can-fixture intercepts an AJAX request and simulates the response with a file or function.">can-fixture</a>. It return a successful response if the request body has a <code>number</code> and <code>pin</code> and a 400 if not.</li> <li>Use <a href="../can-define/map/map.html" title="Create observable objects.">can-define/map/map</a> to define the <code>Card</code> model, including: <ul> <li>a <code>number</code> and <code>pin</code> property.</li> <li>a <code>state</code> property initialized to <code>unverified</code> that is not part of the card's <a href="../can-define.types.serialize.html" title="Defines custom serialization behavior for a property.">serialize</a>d data.</li> <li>a <code>verify</code> method that posts the card's data to <code>/verifyCard</code> and updates the <code>state</code> accordingly.</li> </ul></li> </ul> <pre><code class="language-js">// ======================================== // CODE // ======================================== can.fixture({ &quot;/verifyCard&quot;: function(request, response) { if (!request.data || !request.data.number || !request.data.pin) { response(400, {}); } else { return {}; } } }); can.fixture.delay = 1000; var Card = can.DefineMap.extend({ number: &quot;string&quot;, pin: &quot;string&quot;, state: { value: &quot;unverified&quot;, serialize: false }, verify: function() { this.state = &quot;verifying&quot;; var self = this; return can.ajax({ type: &quot;POST&quot;, url: &quot;/verifyCard&quot;, data: this.serialize() }).then( function() { self.state = &quot;verified&quot;; return self; }, function() { self.state = &quot;invalid&quot;; return self; }); } }); var ATM = can.DefineMap.extend({ state: {type: &quot;string&quot;, value: &quot;readingCard&quot;} }); can.Component.extend({ tag: &quot;atm-machine&quot;, view: can.stache.from(&quot;atm-template&quot;), ViewModel: ATM, }); document.body.insertBefore( can.stache.from(&quot;app-template&quot;)({}), document.body.firstChild ); // ======================================== // TESTS // ======================================== QUnit.module(&quot;ATM system&quot;, { setup: function() { can.fixture.delay = 1; }, teardown: function() { can.fixture.delay = 2000; } }); QUnit.asyncTest(&quot;Valid Card&quot;, function() { var c = new Card({ number: &quot;01234567890&quot;, pin: 1234 }); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;verified&quot;, &quot;card is verified&quot;); QUnit.start(); }); }); QUnit.asyncTest(&quot;Invalid Card&quot;, function() { var c = new Card({}); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;invalid&quot;, &quot;card is invalid&quot;); QUnit.start(); }); }); </code></pre> <p><span line-highlight='5-42,only'></span> When complete, all tests should pass.</p> <p>In this step, you implemented a <code>Card</code> model that encapsulates the behavior of its own state.</p> <h2>Deposit test</h2> <p>In this section, we will:</p> <ul> <li>Design an API retrieving <code>Account</code>s.</li> <li>Design an API for a <code>Deposit</code> type.</li> <li>Write out tests for the <code>Deposit</code> type.</li> </ul> <p>An <code>Account</code> will have an <code>id</code>, <code>name</code>, and <code>balance</code>. We'll use <a href="../can-connect.html" title="can-connect provides persisted data middleware. Use it to assemble powerful model layers for any JavaScript project.">can-connect</a> to add a <a href="../can-connect/can/map/map.getList.html" title="Gets a list of instances of the map type.">getList</a> method that retrieves an account given a <code>card</code>.</p> <p>A <code>Deposit</code> will take a <code>card</code>, an <code>amount</code>, and an <code>account</code>. Deposits will start out having a <code>state</code> of <code>&quot;invalid&quot;</code>. When the deposit has a <code>card</code>, <code>amount</code> and <code>account</code>, the <code>state</code> will change to <code>&quot;ready&quot;</code>. Once the deposit is ready, the <code>.execute()</code> method will change the state to <code>&quot;executing&quot;</code> and then to <code>&quot;executed&quot;</code> once the transaction completes.</p> <p>Update the <code>JS</code> tab to:</p> <ul> <li>Create a <code>deposit</code> with an <code>amount</code> and a <code>card</code>.</li> <li>Check that the <code>state</code> is <code>&quot;invalid&quot;</code> because there is no <code>account</code>.</li> <li>Use <code>Account.getList</code> to get the accounts for the card and: <ul> <li>set the <code>deposit.accounts</code> to the first account.</li> <li>remember the starting <code>balance</code>.</li> </ul></li> <li>Use <a href="../can-define/map/map.prototype.on.html" title="Add event handlers to a map.">on</a> to listen for <code>state</code> changes. When <code>state</code> is: <ul> <li><code>&quot;ready&quot;</code>, <code>.execute()</code> the transaction.</li> <li><code>&quot;executed&quot;</code>, verify the new account balance.</li> </ul></li> </ul> <pre><code class="language-js">// ======================================== // CODE // ======================================== can.fixture({ &quot;/verifyCard&quot;: function(request, response) { if (!request.data || !request.data.number || !request.data.pin) { response(400, {}); } else { return {}; } } }); can.fixture.delay = 1000; var Card = can.DefineMap.extend({ number: &quot;string&quot;, pin: &quot;string&quot;, state: { value: &quot;unverified&quot;, serialize: false }, verify: function() { this.state = &quot;verifying&quot;; var self = this; return can.ajax({ type: &quot;POST&quot;, url: &quot;/verifyCard&quot;, data: this.serialize() }).then( function() { self.state = &quot;verified&quot;; return self; }, function() { self.state = &quot;invalid&quot;; return self; }); } }); var ATM = can.DefineMap.extend({ state: {type: &quot;string&quot;, value: &quot;readingCard&quot;} }); can.Component.extend({ tag: &quot;atm-machine&quot;, view: can.stache.from(&quot;atm-template&quot;), ViewModel: ATM, }); document.body.insertBefore( can.stache.from(&quot;app-template&quot;)({}), document.body.firstChild ); // ======================================== // TESTS // ======================================== QUnit.module(&quot;ATM system&quot;, { setup: function() { can.fixture.delay = 1; }, teardown: function() { can.fixture.delay = 2000; } }); QUnit.asyncTest(&quot;Valid Card&quot;, function() { var c = new Card({ number: &quot;01234567890&quot;, pin: 1234 }); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;verified&quot;, &quot;card is verified&quot;); QUnit.start(); }); }); QUnit.asyncTest(&quot;Invalid Card&quot;, function() { var c = new Card({}); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;invalid&quot;, &quot;card is invalid&quot;); QUnit.start(); }); }); QUnit.asyncTest(&quot;Deposit&quot;, 6, function() { var card = new Card({ number: &quot;0123456789&quot;, pin: &quot;1122&quot; }); var deposit = new Deposit({ amount: 100, card: card }); equal(deposit.state, &quot;invalid&quot;); var startingBalance; Account.getList(card.serialize()).then(function(accounts) { QUnit.ok(true, &quot;got accounts&quot;); deposit.account = accounts[0]; startingBalance = accounts[0].balance; }); deposit.on(&quot;state&quot;, function(ev, newVal) { if (newVal === &quot;ready&quot;) { QUnit.ok(true, &quot;deposit is ready&quot;); deposit.execute(); } else if (newVal === &quot;executing&quot;) { QUnit.ok(true, &quot;executing a deposit&quot;); } else if (newVal === &quot;executed&quot;) { QUnit.ok(true, &quot;executed a deposit&quot;); equal(deposit.account.balance, 100 + startingBalance); start(); } }); }); </code></pre> <p><span line-highlight='111-151,only'></span> When complete, the <strong>Deposit</strong> test should run, but error because <em>Deposit is not defined</em>.</p> <blockquote> <p><strong>Optional:</strong> Challenge yourself by writing the <strong>Withdrawal</strong> test on your own. How is it different than the <strong>Deposit</strong> test?</p> </blockquote> <h2>Transaction, Deposit, and Withdrawal models</h2> <p>In this section, we will:</p> <ul> <li>Implement the <code>Account</code> model.</li> <li>Implement a base <code>Transaction</code> model and extend it into a <code>Deposit</code> and <code>Withdrawal</code> model.</li> <li>Get the <strong>Deposit</strong> test to pass.</li> </ul> <p>Update the <code>JavaScript</code> tab to:</p> <ul> <li>Simulate <code>/accounts</code> to return <code>Account</code> data with <a href="../can-fixture.html" title="can-fixture intercepts an AJAX request and simulates the response with a file or function.">can-fixture</a>.</li> <li>Simulate <code>/deposit</code> to always return a successful result.</li> <li>Simulate <code>/withdrawal</code> to always return a successful result.</li> <li>Define the <code>Account</code> model to: <ul> <li>have an <code>id</code> property</li> <li>have a <code>balance</code> property</li> <li>have a <code>name</code> property</li> </ul></li> <li>Define an <code>Account.List</code> type with <a href="../can-define/list/list.html" title="Create observable lists.">can-define/list/list</a></li> <li>Connect <code>Account</code> and <code>Account.List</code> types to the restful <code>&quot;/accounts&quot;</code> endpoint using <a href="../can-connect/can/base-map/base-map.html" title="Create connection with many of the best behaviors in can-connect and hook it up to a map.">can-connect/can/base-map/base-map</a>.</li> <li>Define the <code>Transaction</code> model to: <ul> <li>have an <code>account</code> and <code>card</code> property.</li> <li>have an <code>executing</code> and <code>executed</code> property that track if the transaction is executing or has executed.</li> <li>have a <code>rejected</code> property that stores the error given for a failed transaction.</li> <li>have an <strong>abstract</strong> <code>ready</code> property that <code>Deposit</code> and <code>Withdrawal</code> will implement to return <code>true</code> when the transaction is in a state able to be executed.</li> <li>have a <code>state</code> property that reads other stateful properties and returns a string representation of the state.</li> <li>have an <strong>abstract</strong> <code>executeStart</code> method that <code>Deposit</code> and <code>Withdrawal</code> will implement to execute the transaction and return a <code>Promise</code> the resolves when the transaction completes.</li> <li>have an <strong>abstract</strong> <code>executeEnd</code> method that <code>Deposit</code> and <code>Withdrawal</code> will implement to update the transactions values (typically the <code>account</code> balance) if the transaction completed successfully.</li> <li>have an <code>execute</code> method that calls <code>.executeStart()</code> and <code>executeEnd()</code> and keeps the stateful properties updated correctly.</li> </ul></li> <li>Define the <code>Deposit</code> model to: <ul> <li>have an <code>amount</code> property.</li> <li>implement <code>ready</code> to return <code>true</code> when the amount is greater than <code>0</code> and there's an <code>account</code> and <code>card</code>.</li> <li>implement <code>executeStart</code> to <code>POST</code> the deposit information to <code>/deposit</code></li> <li>implement <code>executeEnd</code> to update the account balance.</li> </ul></li> <li>Define the <code>Withdrawal</code> model to behave in the same way as <code>Deposit</code> except that it <code>POST</code>s the withdrawal information to <code>/withdrawal</code>.</li> </ul> <pre><code class="language-js">// ======================================== // CODE // ======================================== can.fixture({ &quot;/verifyCard&quot;: function(request, response) { if (!request.data || !request.data.number || !request.data.pin) { response(400, {}); } else { return {}; } }, &quot;/accounts&quot;: function() { return { data: [{ balance: 100, id: 1, name: &quot;checking&quot; }, { balance: 10000, id: 2, name: &quot;savings&quot; }] }; }, &quot;/deposit&quot;: function() { return {}; }, &quot;/withdrawal&quot;: function() { return {}; } }); can.fixture.delay = 1000; var Card = can.DefineMap.extend({ number: &quot;string&quot;, pin: &quot;string&quot;, state: { value: &quot;unverified&quot;, serialize: false }, verify: function() { this.state = &quot;verifying&quot;; var self = this; return can.ajax({ type: &quot;POST&quot;, url: &quot;/verifyCard&quot;, data: this.serialize() }).then( function() { self.state = &quot;verified&quot;; return self; }, function() { self.state = &quot;invalid&quot;; return self; }); } }); var Account = can.DefineMap.extend(&quot;Account&quot;, { id: &quot;number&quot;, balance: &quot;number&quot;, name: &quot;string&quot; }); Account.List = can.DefineList.extend(&quot;AccountList&quot;, { &quot;*&quot;: Account }); can.connect.baseMap({ url: &quot;/accounts&quot;, Map: Account, List: Account.List, name: &quot;accounts&quot; }); var Transaction = can.DefineMap.extend({ account: Account, card: Card, executing: { type: &quot;boolean&quot;, value: false }, executed: { type: &quot;boolean&quot;, value: false }, rejected: &quot;any&quot;, get ready(){ throw new Error(&quot;Transaction::ready must be provided by extended type&quot;); }, get state() { if (this.rejected) { return &quot;rejected&quot;; } if (this.executed) { return &quot;executed&quot;; } if (this.executing) { return &quot;executing&quot;; } // make sure there's an amount, account, and card if (this.ready) { return &quot;ready&quot;; } return &quot;invalid&quot;; }, executeStart: function(){ throw new Error(&quot;Transaction::executeStart must be provided by extended type&quot;); }, executeEnd: function(){ throw new Error(&quot;Transaction::executeEnd must be provided by extended type&quot;); }, execute: function() { if (this.state === &quot;ready&quot;) { this.executing = true; var def = this.executeStart(), self = this; def.then(function() { can.batch.start(); self.set({ executing: false, executed: true }); self.executeEnd(); can.batch.stop(); }, function(reason){ self.set({ executing: false, executed: true, rejected: reason }); }); } } }); var Deposit = Transaction.extend({ amount: &quot;number&quot;, get ready() { return this.amount &gt; 0 &amp;&amp; this.account &amp;&amp; this.card; }, executeStart: function() { return can.ajax({ type: &quot;POST&quot;, url: &quot;/deposit&quot;, data: { card: this.card.serialize(), accountId: this.account.id, amount: this.amount } }); }, executeEnd: function(data) { this.account.balance = this.account.balance + this.amount; } }); var Withdrawal = Transaction.extend({ amount: &quot;number&quot;, get ready() { return this.amount &gt; 0 &amp;&amp; this.account &amp;&amp; this.card; }, executeStart: function() { return can.ajax({ type: &quot;POST&quot;, url: &quot;/withdrawal&quot;, data: { card: this.card.serialize(), accountId: this.account.id, amount: this.amount } }); }, executeEnd: function(data) { this.account.balance = this.account.balance - this.amount; } }); var ATM = can.DefineMap.extend({ state: {type: &quot;string&quot;, value: &quot;readingCard&quot;} }); can.Component.extend({ tag: &quot;atm-machine&quot;, view: can.stache.from(&quot;atm-template&quot;), ViewModel: ATM, }); document.body.insertBefore( can.stache.from(&quot;app-template&quot;)({}), document.body.firstChild ); // ======================================== // TESTS // ======================================== QUnit.module(&quot;ATM system&quot;, { setup: function() { can.fixture.delay = 1; }, teardown: function() { can.fixture.delay = 2000; } }); QUnit.asyncTest(&quot;Valid Card&quot;, function() { var c = new Card({ number: &quot;01234567890&quot;, pin: 1234 }); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;verified&quot;, &quot;card is verified&quot;); QUnit.start(); }); }); QUnit.asyncTest(&quot;Invalid Card&quot;, function() { var c = new Card({}); QUnit.equal(c.state, &quot;unverified&quot;); c.verify(); QUnit.equal(c.state, &quot;verifying&quot;, &quot;card is verifying&quot;); c.on(&quot;state&quot;, function(ev, newVal) { QUnit.equal(newVal, &quot;invalid&quot;, &quot;card is invalid&quot;); QUnit.start(); }); }); QUnit.asyncTest(&quot;Deposit&quot;, 6, function() { var card = new Card({ number: &quot;0123456789&quot;, pin: &quot;1122&quot; }); var deposit = new Deposit({ amount: 100, card: card }); equal(deposit.state, &quot;invalid&quot;); var startingBalance; Account.getList(card.serialize()).then(function(accounts) { QUnit.ok(true, &quot;got accounts&quot;); deposit.account = accounts[0]; startingBalance = accounts[0].balance; }); deposit.on(&quot;state&quot;, function(ev, newVal) { if (newVal === &quot;ready&quot;) { QUnit.ok(true, &quot;deposit is ready&quot;); deposit.execute(); } else if (newVal === &quot;executing&quot;) { QUnit.ok(true, &quot;executing a deposit&quot;); } else if (newVal === &quot;executed&quot;) { QUnit.ok(true, &quot;executed a deposit&quot;); equal(deposit.account.balance, 100 + startingBalance); start(); } }); }); </code></pre> <p><span line-highlight='13-31,63-187,only'></span> When complete, the <strong>Deposit</strong> tests will pass.</p> <h2>Reading Card page and test</h2> <p>In this section, we will:</p> <ul> <li>Allow the user to enter a card number and go to the <strong>Reading Pin</strong> page.</li> <li>Add tests to <strong>ATM Basics</strong> test.</li> </ul> <p>Update the <code>HTML</code> tab to:</p> <ul> <li>Allow a user to call <code>cardNumber</code> with the <code>&lt;input&gt;</code>'s <code>value</code>.</li> </ul> <pre><code class="language-html">&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta name=&quot;description&quot; content=&quot;CanJS 3.0 - ATM Guide - Setup&quot;&gt; &lt;meta charset=&quot;utf-8&quot;&gt; &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot;&gt; &lt;title&gt;JS Bin&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;script type='text/stache' id='atm-template'&gt; &lt;div class=&quot;screen&quot;&gt; &lt;div class=&quot;screen-content&quot;&gt; &lt;div class=&quot;screen-glass&quot;&gt; {{#switch state}} {{#case &quot;readingCard&quot;}} &lt;h2&gt;Reading Card&lt;/h2&gt; &lt;p&gt;Welcome to canATM where there are &lt;strong&gt;never&lt;/strong&gt; fees!&lt;/p&gt; &lt;/p&gt; &lt;p&gt; Enter Card Number: &lt;input name=&quot;card&quot; ($enter)=&quot;cardNumber(%element.value)&quot;/&gt; &lt;/p&gt; {{/case}} {{#case &quot;readingPin&quot;}} &lt;h2&gt;Reading Pin&lt;/h2&gt; {{/case}} {{#case &quot;choosingTransaction&quot;}} &lt;h2&gt;Choose Transaction&lt;/h2&gt; {{/case}} {{#case &quot;pickingAccount&quot;}} &lt;h2&gt;Pick Account&lt;/h2&gt; {{/case}} {{#case &quot;depositInfo&quot;}} &lt;h2&gt;Deposit&lt;/h2&gt; {{/case}} {{#case &quot;withdrawalInfo&quot;}} &lt;h2&gt;Withdraw&lt;/h2&gt; {{/case}} {{#case &quot;successfulTransaction&quot;}} &lt;h2&gt;Transaction Successful!&lt;/h2&gt; {{/case}} {{#case &quot;printingReceipt&quot;}} &lt;h2&gt;Printing Receipt&lt;/h2&gt; {{/case}} {{#default}} &lt;h2&gt;Error&lt;/h2&gt; &lt;p&gt;Invalid state - {{state}}&lt;/p&gt; {{/default}} {{/switch}} &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/script&gt; &lt;script type='text/stache' id='app-template'&gt; &lt;div class=&quot;title&quot;&gt; &lt;h1&gt;canATM&lt;/h1&gt; &lt;/div&gt; &lt;atm-machine/&gt; &lt;/script&gt; &lt;script src=&quot;https://code.jquery.com/jquery-2.2.4.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;https://unpkg.com/can/dist/global/can.all.js&quot;&gt;&lt;/script&gt; &lt;div id=&quot;qunit&quot;&gt;&lt;/div&gt; &lt;link rel=&quot;stylesheet&quot; href=&quot;http://code.jquery.com/qunit/qunit-1.12.0.css&quot;&gt; &lt;script src=&quot;http://code.jquery.com/qunit/qunit-1.12.0.js&quot;&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p><span line-highlight='20-26,only'></span> Update the <code>JavaScript</code> tab to:</p> <ul> <li>Declare a <code>card</code> property.</li> <li>Derive a <code>state</code> property that changes to <code>&quot;readingPin&quot;</code> when <code>card</code> is defined.</li> <li>Add a <code>cardNumber</code> that creates a <code>card</code> with the <code>number</code> provided.</li> </ul> <pre><code class="language-js">// ======================================== // CODE // ======================================== can.fixture({ &quot;/verifyCard&quot;: function(request, response) { if (!request.data || !request.data.number || !request.data.pin) { response(400, {}); } else { return {}; } }, &quot;/accounts&quot;: function() { return { data: [{ balance: 100, id: 1, name: &quot;checking&quot; }, { balance: 10000, id: 2, name: &quot;savings&quot; }] }; }, &quot;/deposit&quot;: function() { return {}; }, &quot;/withdrawal&quot;: function() { return {}; } }); can.fixture.delay = 1000; var Card = can.DefineMap.extend({ number: &quot;string&quot;, pin: &quot;string&quot;, state: { value: &quot;unverified&quot;, serialize: false }, verify: function() { this.state = &quot;verifying&quot;; var self = this; return can.ajax({ type: &quot;POST&quot;, url: &quot;/verifyCard&quot;, data: this.serialize() }).then( function() { self.state = &quot;verified&quot;; return self; }, function() { self.state = &quot;invalid&quot;; return self; }); } }); var Account = can.DefineMap.extend(&quot;Account&quot;, { id: &quot;number&quot;, balance: &quot;number&quot;, name: &quot;string&quot; }); Account.List = can.DefineList.extend(&quot;AccountList&quot;, { &quot;*&quot;: Account }); can.connect.baseMap({ url: &quot;/accounts&quot;, Map: Account, List: Account.List, name: &quot;accounts&quot; }); var Transaction = can.DefineMap.extend({ account: Account, card: Card, executing: { type: &quot;boolean&quot;, value: false }, executed: { type: &quot;boolean&quot;, value: false }, rejected: &quot;any&quot;, get ready(){ throw new Error(&quot;Transaction::ready must be provided by extended type&quot;); }, get state() { i