├── .gitignore ├── Assetfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── config.ru ├── generators └── license.js ├── package.json ├── packages ├── ember │ └── lib │ │ └── main.js ├── sproutcore-statechart │ ├── lib │ │ ├── core.js │ │ ├── debug.js │ │ ├── debug │ │ │ ├── monitor.js │ │ │ └── sequence_matcher.js │ │ ├── ext.js │ │ ├── ext │ │ │ └── function.js │ │ ├── main.js │ │ ├── mixins │ │ │ ├── delegate_support.js │ │ │ └── statechart_delegate.js │ │ ├── system.js │ │ └── system │ │ │ ├── async.js │ │ │ ├── empty_state.js │ │ │ ├── history_state.js │ │ │ ├── state.js │ │ │ ├── state_route_handler_context.js │ │ │ └── statechart.js │ └── tests │ │ ├── event_handling │ │ ├── advanced │ │ │ ├── respond_to_event_test.js │ │ │ └── without_concurrent_states_test.js │ │ └── basic │ │ │ ├── with_concurrent_states_test.js │ │ │ └── without_concurrent_states_test.js │ │ ├── state │ │ ├── initial_substate_test.js │ │ ├── is_current_state_test.js │ │ ├── methods │ │ │ └── rout_triggered.js │ │ ├── namespacing_test.js │ │ ├── plugin │ │ │ ├── mixin_test.js │ │ │ └── nesting_test.js │ │ └── state_observes_test.js │ │ ├── state_transitioning │ │ ├── async │ │ │ ├── core_test.js │ │ │ ├── with_concurrent_states_test.js │ │ │ └── without_concurrent_states_test.js │ │ ├── history_state │ │ │ ├── initial_substate │ │ │ │ ├── core_test.js │ │ │ │ └── without_concurrent_states_test.js │ │ │ └── standard │ │ │ │ ├── with_concurrent_states_test.js │ │ │ │ └── without_concurrent_states │ │ │ │ ├── context_test.js │ │ │ │ └── core_test.js │ │ ├── standard │ │ │ ├── with_concurrent_states │ │ │ │ ├── advanced_test.js │ │ │ │ ├── basic_test.js │ │ │ │ └── intermediate_test.js │ │ │ └── without_concurrent_states │ │ │ │ ├── context_test.js │ │ │ │ └── core_test.js │ │ └── transient │ │ │ └── without_concurrent_states_test.js │ │ ├── statechart │ │ ├── create │ │ │ ├── assigned_root_state_test.js │ │ │ └── unassigned_root_state_test.js │ │ ├── destroy_test.js │ │ ├── invoke_state_method_test.js │ │ └── owner_test.js │ │ └── system │ │ └── state_route_handler_context │ │ └── methods │ │ └── retry.js └── sproutcore-utils │ └── lib │ ├── main.js │ ├── responds_to.js │ └── try_to_perform.js └── tests ├── index.html ├── jquery-1.7.1.js ├── minispade.js └── qunit ├── qunit.css ├── qunit.js └── run-qunit.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.bpkg 2 | *.gem 3 | *.rbc 4 | .DS_Store 5 | .bpm 6 | .bundle 7 | .config 8 | .yardoc 9 | InstalledFiles 10 | _site 11 | _yardoc 12 | assets 13 | assets/bpm_libs.js 14 | assets/bpm_styles.css 15 | coverage 16 | dist 17 | doc/ 18 | docs/jsdoc 19 | docs/output 20 | lib/*/tests/all.js 21 | lib/*/tests/qunit* 22 | lib/bundler/man 23 | pkg 24 | rdoc 25 | spade-boot.js 26 | spec/reports 27 | test/tmp 28 | test/version_tmp 29 | test_*.html 30 | tmp 31 | tmp*.gem 32 | tmp.bpm 33 | tmp.spade 34 | tests/source 35 | -------------------------------------------------------------------------------- /Assetfile: -------------------------------------------------------------------------------- 1 | require "rake-pipeline-web-filters" 2 | 3 | input "packages" 4 | output "tests/source" 5 | 6 | match "*/{lib,tests}/**/*.js" do 7 | minispade :rewrite_requires => true, :module_id_generator => proc { |input| 8 | id = input.path.dup 9 | id.sub!('/lib/', '/') 10 | id.sub!(/\.js$/, '') 11 | id.sub!(/\/main$/, '') 12 | id.sub!('/tests', '/~tests') 13 | id 14 | } 15 | 16 | concat do |filename| 17 | filename =~ %r{/tests/} ? "ember-tests.js" : "ember.js" 18 | end 19 | end 20 | 21 | # Hack to ignore certain files 22 | match "**/*.{json,md}" do 23 | concat "trash" 24 | end 25 | 26 | match "**/README" do 27 | concat "trash" 28 | end 29 | 30 | match "*/*.js" do 31 | concat "trash" 32 | end 33 | 34 | 35 | # vim: filetype=ruby 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "sproutcore", :git => "https://github.com/wycats/abbot-from-scratch.git" 4 | gem "uglifier" 5 | gem "execjs" 6 | gem "multi_json" 7 | gem "rake" 8 | gem "rake-pipeline", :git => "https://github.com/livingsocial/rake-pipeline.git" 9 | gem "rake-pipeline-web-filters", :git => "https://github.com/wycats/rake-pipeline-web-filters.git" -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/livingsocial/rake-pipeline.git 3 | revision: 5ab27c33bcecdc6f156c2e1ead5d5e831bc1acbd 4 | specs: 5 | rake-pipeline (0.5.0) 6 | rake (~> 0.9.0) 7 | thor 8 | 9 | GIT 10 | remote: https://github.com/wycats/abbot-from-scratch.git 11 | revision: 29dfaa6c3c120847e61f742f74c414e4872cc142 12 | specs: 13 | sproutcore (0.0.1) 14 | 15 | GIT 16 | remote: https://github.com/wycats/rake-pipeline-web-filters.git 17 | revision: 32428ba45bbc74e02287a7cf483004df71d89533 18 | specs: 19 | rake-pipeline-web-filters (0.5.0) 20 | rack 21 | rake-pipeline 22 | 23 | GEM 24 | remote: http://rubygems.org/ 25 | specs: 26 | execjs (1.2.6) 27 | multi_json (~> 1.0) 28 | multi_json (1.0.3) 29 | rack (1.4.1) 30 | rake (0.9.2) 31 | thor (0.14.6) 32 | uglifier (1.0.3) 33 | execjs (>= 0.3.0) 34 | multi_json (>= 1.0.2) 35 | 36 | PLATFORMS 37 | ruby 38 | 39 | DEPENDENCIES 40 | execjs 41 | multi_json 42 | rake 43 | rake-pipeline! 44 | rake-pipeline-web-filters! 45 | sproutcore! 46 | uglifier 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SproutCore (Ember.js) Statechart 2 | 3 | ### Description: 4 | 5 | This is a port of the SproutCore Statechart library from the SproutCore 1.x 6 | framework for use in SproutCore 2.0 (Ember.js) applications. It was principally authored 7 | by Michael Cohen (aka. FrozenCanuck). 8 | 9 | ### Synopsis: 10 | 11 | TODO: Add documentation to the README. 12 | 13 | For the time being, there are plenty of examples in the source code, and example 14 | usage throughout the extensive unit tests. 15 | 16 | ### Learning by Example 17 | 18 | We are working hard to provide more documentation and examples for Ember.js and here specifically for 19 | the statechart framework. Until there is more, you can learn from this example: 20 | 21 | [Quick Notes - Example Ember.js Application for statecharts and routing support](https://github.com/DominikGuzei/ember-routing-statechart-example) 22 | 23 | ### Building sproutcore-statechart.js 24 | 25 | You need ruby and some gems to build the source code and to run unit tests. 26 | 27 | 1. Install Ruby 1.9.2+. There are many resources on the web can help; one of the best is [rvm](http://rvm.beginrescueend.com/). 28 | 29 | 2. Install Bundler: `gem install bundler` 30 | 31 | 3. Run `bundle` inside the project root to install the gem dependencies. 32 | 33 | 4. Run `rake` to build sproutcore-statechart.js. Two builds will be placed in the `dist/` directory. 34 | 35 | 5. `sproutcore-statechart.js` and `sproutcore-statechart.min.js` - unminified and minified builds of sproutcore-statechart.js 36 | 37 | If you are building under Linux, you will need a JavaScript runtime for 38 | minification. You can either install nodejs or `gem install 39 | therubyracer`. 40 | 41 | ### Running / Writing Unit Tests: 42 | 43 | The repository has been refactored to use the same structure as the [Ember.js](https://github.com/emberjs/ember.js) project: 44 | 45 | 1. To start the development server, run `bundle exec rackup`. 46 | 47 | 2. Then visit: [http://localhost:9292/tests/index.html?package=all](http://localhost:9292/tests/index.html?package=all) 48 | 49 | You can also pass `jquery=VERSION` in the test URL to test different versions of jQuery. Default is 1.7.1. 50 | 51 | ### Features / Problems: 52 | 53 | * The `stateObserves()` helper does not properly work with global paths. These 54 | problems are captured in the unit tests 55 | * There are a few fixes for bugs that are in the SproutCore 1.x version that 56 | have not been ported over as of yet. 57 | * The Unit Tests for route triggered are failing at the moment, but it works -> needs to be fixed! 58 | 59 | If you run into a problem, please file an issue on this repository. 60 | 61 | ### Requirements: 62 | 63 | In order to use the Statechart package, you'll need two libraries: 64 | 65 | * [SproutCore 2.0's runtime library](https://github.com/sproutcore/sproutcore20) 66 | * [SproutCore 2.0's utils library](https://github.com/sproutcore/sproutcore-utils) 67 | 68 | These dependencies are automatically installed if you're using BPM. If you want to download 69 | and use the JS file (and assuming you're already using SproutCore 2.0), don't forget to grab 70 | the built version of the Utils package. 71 | 72 | You can also simply include a built version of Ember.js before ember-statechart.js 73 | 74 | ### Install: 75 | 76 | You can use the Statechart package by either downloading the JavaScript files 77 | provided in the [Downloads section](https://github.com/sproutcore/sproutcore-statechart/archives/master) 78 | of this repository or using [BPM](http://getbpm.org/). To install it via BPM, simply run 79 | 80 | bpm add sproutcore-statechart 81 | 82 | And this will handle adding all of its dependencies as well. You, of course, need to 83 | be using BPM for your application or package for this to work ;) 84 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "erb" 3 | require "uglifier" 4 | require "sproutcore" 5 | 6 | LICENSE = File.read("generators/license.js") 7 | 8 | module SproutCore 9 | module Compiler 10 | class Entry 11 | def body 12 | "\n(function(exports) {\n#{@raw_body}\n})({});\n" 13 | end 14 | end 15 | end 16 | end 17 | 18 | ## HELPERS ## 19 | 20 | def strip_require(file) 21 | result = File.read(file) 22 | result.gsub!(%r{^\s*require\(['"]([^'"])*['"]\);?\s*}, "") 23 | result 24 | end 25 | 26 | def strip_ember_assert(file) 27 | result = File.read(file) 28 | result.gsub!(%r{^(\s)+ember_assert\((.*)\).*$}, "") 29 | result 30 | end 31 | 32 | def uglify(file) 33 | uglified = Uglifier.compile(File.read(file)) 34 | "#{LICENSE}\n#{uglified}" 35 | end 36 | 37 | SproutCore::Compiler.intermediate = "tmp/intermediate" 38 | SproutCore::Compiler.output = "tmp/static" 39 | 40 | # Create a compile task for an Ember package. This task will compute 41 | # dependencies and output a single JS file for a package. 42 | def compile_package_task(input, output=input) 43 | js_tasks = SproutCore::Compiler::Preprocessors::JavaScriptTask.with_input "packages/#{input}/lib/**/*.js", "." 44 | SproutCore::Compiler::CombineTask.with_tasks js_tasks, "#{SproutCore::Compiler.intermediate}/#{output}" 45 | end 46 | 47 | ## TASKS ## 48 | 49 | # Create ember:package tasks for each of the Ember packages 50 | namespace :sproutcore do 51 | %w(statechart).each do |package| 52 | task package => compile_package_task("sproutcore-#{package}", "sproutcore-#{package}") 53 | end 54 | end 55 | 56 | # Create a build task that depends on all of the package dependencies 57 | task :build => ["sproutcore:statechart"] 58 | 59 | distributions = { 60 | "sproutcore-statechart" => ["sproutcore-statechart"] 61 | } 62 | 63 | distributions.each do |name, libraries| 64 | # Strip out require lines. For the interim, requires are 65 | # precomputed by the compiler so they are no longer necessary at runtime. 66 | file "dist/#{name}.js" => :build do 67 | puts "Generating #{name}.js" 68 | 69 | mkdir_p "dist" 70 | 71 | File.open("dist/#{name}.js", "w") do |file| 72 | libraries.each do |library| 73 | file.puts strip_require("tmp/static/#{library}.js") 74 | end 75 | end 76 | end 77 | 78 | # Minified distribution 79 | file "dist/#{name}.min.js" => "dist/#{name}.js" do 80 | require 'zlib' 81 | 82 | print "Generating #{name}.min.js... " 83 | STDOUT.flush 84 | 85 | File.open("dist/#{name}.prod.js", "w") do |file| 86 | file.puts strip_ember_assert("dist/#{name}.js") 87 | end 88 | 89 | minified_code = uglify("dist/#{name}.prod.js") 90 | File.open("dist/#{name}.min.js", "w") do |file| 91 | file.puts minified_code 92 | end 93 | 94 | gzipped_kb = Zlib::Deflate.deflate(minified_code).bytes.count / 1024 95 | 96 | puts "#{gzipped_kb} KB gzipped" 97 | 98 | rm "dist/#{name}.prod.js" 99 | end 100 | end 101 | 102 | 103 | desc "Build Ember.js" 104 | task :dist => distributions.keys.map {|name| "dist/#{name}.min.js"} 105 | 106 | desc "Clean build artifacts from previous builds" 107 | task :clean do 108 | sh "rm -rf tmp && rm -rf dist" 109 | end 110 | 111 | task :default => :dist -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline' 2 | require 'rake-pipeline/middleware' 3 | 4 | use Rake::Pipeline::Middleware, "Assetfile" 5 | run Rack::Directory.new('.') 6 | -------------------------------------------------------------------------------- /generators/license.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore - JavaScript Application Framework 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sproutcore-statechart", 3 | "summary": "SproutCore Statecharts", 4 | "description": "The SproutCore Statechart library", 5 | "homepage": "http://sproutcore.com", 6 | "author": "Strobe Inc., Apple, Inc., and contributors", 7 | "version": "2.0.beta.4", 8 | "bpm": "1.0.0", 9 | "directories": { 10 | "lib": "lib" 11 | }, 12 | "dependencies": { 13 | "spade": "~> 1.0", 14 | "ember-runtime": ">= 0", 15 | "sproutcore-utils": "2.0.beta.3" 16 | }, 17 | "dependencies:development": { 18 | "spade-qunit": "~> 1.0.0" 19 | }, 20 | "bpm:build": { 21 | "bpm_libs.js": { 22 | "directories": [ 23 | "lib" 24 | ], 25 | "modes": "*" 26 | }, 27 | "sproutcore-statechart/bpm_tests.js": { 28 | "directories": [ 29 | "tests" 30 | ], 31 | "modes": [ 32 | "debug" 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/core.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore Statechart 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | SC.handleActions = function(func) { 9 | var args = Array.prototype.slice.call(arguments); 10 | // remove func 11 | args.shift(); 12 | 13 | func.isActionHandler = YES; 14 | func.actions = args; 15 | return func; 16 | }; 17 | 18 | SC.stateObserves = function(func) { 19 | var args = Array.prototype.slice.call(arguments); 20 | // remove func 21 | args.shift(); 22 | 23 | func.isStateObserveHandler = YES; 24 | func.args = args; 25 | return func; 26 | }; -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/debug.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore Statechart 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | require('sproutcore-statechart/debug/monitor'); 9 | require('sproutcore-statechart/debug/sequence_matcher'); 10 | -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/debug/monitor.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SC.Statechart - A Statechart Framework for SproutCore 3 | // Copyright: ©2010, 2011 Michael Cohen, and contributors. 4 | // Portions @2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | /*globals SC */ 8 | 9 | require('sproutcore-statechart/debug/sequence_matcher'); 10 | 11 | SC.StatechartMonitor = SC.Object.extend({ 12 | 13 | statechart: null, 14 | 15 | sequence: null, 16 | 17 | init: function() { 18 | this.reset(); 19 | }, 20 | 21 | reset: function() { 22 | this.propertyWillChange('length'); 23 | this.sequence = []; 24 | this.propertyDidChange('length'); 25 | }, 26 | 27 | length: function() { 28 | return this.sequence.length; 29 | }.property(), 30 | 31 | pushEnteredState: function(state) { 32 | this.propertyWillChange('length'); 33 | this.sequence.push({ action: 'entered', state: state }); 34 | this.propertyDidChange('length'); 35 | }, 36 | 37 | pushExitedState: function(state) { 38 | this.propertyWillChange('length'); 39 | this.sequence.push({ action: 'exited', state: state }); 40 | this.propertyDidChange('length'); 41 | }, 42 | 43 | matchSequence: function() { 44 | return SC.StatechartSequenceMatcher.create({ 45 | statechartMonitor: this 46 | }); 47 | }, 48 | 49 | matchEnteredStates: function() { 50 | var expected = Array.prototype.slice.call(arguments.length === 1 ? arguments[0] : arguments), 51 | actual = this.getPath('statechart.enteredStates'), 52 | matched = 0, 53 | statechart = this.get('statechart'); 54 | 55 | if (expected.length !== actual.length) return NO; 56 | 57 | expected.forEach(function(item) { 58 | if (SC.typeOf(item) === "string") item = statechart.getState(item); 59 | if (!item) return; 60 | if (statechart.stateIsEntered(item) && item.get('isEnteredState')) matched += 1; 61 | }); 62 | 63 | return matched === actual.length; 64 | }, 65 | 66 | toString: function() { 67 | var seq = "", 68 | i = 0, 69 | len = 0, 70 | item = null; 71 | 72 | seq += "["; 73 | 74 | len = this.sequence.length; 75 | for (i = 0; i < len; i += 1) { 76 | item = this.sequence[i]; 77 | seq += "%@ %@".fmt(item.action, item.state.get('fullPath')); 78 | if (i < len - 1) seq += ", "; 79 | } 80 | 81 | seq += "]"; 82 | 83 | return seq; 84 | } 85 | 86 | }); -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/debug/sequence_matcher.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SC.Statechart - A Statechart Framework for SproutCore 3 | // Copyright: ©2010, 2011 Michael Cohen, and contributors. 4 | // Portions @2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | /*globals SC */ 9 | 10 | SC.StatechartSequenceMatcher = SC.Object.extend({ 11 | 12 | statechartMonitor: null, 13 | 14 | match: null, 15 | 16 | MISMATCH: {}, 17 | 18 | begin: function() { 19 | this._stack = []; 20 | this.beginSequence(); 21 | this._start = this._stack[0]; 22 | return this; 23 | }, 24 | 25 | end: function() { 26 | this.endSequence(); 27 | 28 | if (this._stack.length > 0) { 29 | throw "can not match sequence. sequence matcher has been left in an invalid state"; 30 | } 31 | 32 | var monitor = this.statechartMonitor, 33 | result = this._matchSequence(this._start, 0) === monitor.sequence.length; 34 | 35 | this.set('match', result); 36 | 37 | return result; 38 | }, 39 | 40 | entered: function() { 41 | this._addStatesToCurrentGroup('entered', arguments); 42 | return this; 43 | }, 44 | 45 | exited: function() { 46 | this._addStatesToCurrentGroup('exited', arguments); 47 | return this; 48 | }, 49 | 50 | beginConcurrent: function() { 51 | var group = { 52 | type: 'concurrent', 53 | values: [] 54 | }; 55 | if (this._peek()) this._peek().values.push(group); 56 | this._stack.push(group); 57 | return this; 58 | }, 59 | 60 | endConcurrent: function() { 61 | this._stack.pop(); 62 | return this; 63 | }, 64 | 65 | beginSequence: function() { 66 | var group = { 67 | type: 'sequence', 68 | values: [] 69 | }; 70 | if (this._peek()) this._peek().values.push(group); 71 | this._stack.push(group); 72 | return this; 73 | }, 74 | 75 | endSequence: function() { 76 | this._stack.pop(); 77 | return this; 78 | }, 79 | 80 | _peek: function() { 81 | var len = this._stack.length; 82 | return len === 0 ? null : this._stack[len - 1]; 83 | }, 84 | 85 | _addStatesToCurrentGroup: function(action, states) { 86 | var group = this._peek(), len = states.length, i = 0; 87 | for (; i < len; i += 1) { 88 | group.values.push({ action: action, state: states[i] }); 89 | } 90 | }, 91 | 92 | _matchSequence: function(sequence, marker) { 93 | var values = sequence.values, 94 | len = values.length, 95 | i = 0, val, 96 | monitor = this.statechartMonitor; 97 | 98 | if (len === 0) return marker; 99 | if (marker > monitor.sequence.length) return this.MISMATCH; 100 | 101 | for (; i < len; i += 1) { 102 | val = values[i]; 103 | 104 | if (val.type === 'sequence') { 105 | marker = this._matchSequence(val, marker); 106 | } else if (val.type === 'concurrent') { 107 | marker = this._matchConcurrent(val, marker); 108 | } else if (!this._matchItems(val, monitor.sequence[marker])){ 109 | return this.MISMATCH; 110 | } else { 111 | marker += 1; 112 | } 113 | 114 | if (marker === this.MISMATCH) return this.MISMATCH; 115 | } 116 | 117 | return marker; 118 | }, 119 | 120 | // A 121 | // B (concurrent [X, Y]) 122 | // X 123 | // M 124 | // N 125 | // Y 126 | // O 127 | // P 128 | // C 129 | // 130 | // 0 1 2 3 4 5 6 7 8 131 | // ^ ^ 132 | // A B (X M N) (Y O P) C 133 | // ^ ^ 134 | // A B (Y O P) (X M N) C 135 | 136 | _matchConcurrent: function(concurrent, marker) { 137 | var values = concurrent.values.slice(0), 138 | len = values.length, 139 | i = 0, val, tempMarker = marker, match = false, 140 | monitor = this.statechartMonitor; 141 | 142 | if (len === 0) return marker; 143 | if (marker > monitor.sequence.length) return this.MISMATCH; 144 | 145 | while (values.length > 0) { 146 | for (i = 0; i < len; i += 1) { 147 | val = values[i]; 148 | 149 | if (val.type === 'sequence') { 150 | tempMarker = this._matchSequence(val, marker); 151 | } else if (val.type === 'concurrent') { 152 | tempMarker = this._matchConcurrent(val, marker); 153 | } else if (!this._matchItems(val, monitor.sequence[marker])){ 154 | tempMarker = this.MISMATCH; 155 | } else { 156 | tempMarker = marker + 1; 157 | } 158 | 159 | if (tempMarker !== this.MISMATCH) break; 160 | } 161 | 162 | if (tempMarker === this.MISMATCH) return this.MISMATCH; 163 | values.removeAt(i); 164 | len = values.length; 165 | marker = tempMarker; 166 | } 167 | 168 | return marker; 169 | }, 170 | 171 | _matchItems: function(matcherItem, monitorItem) { 172 | if (!matcherItem || !monitorItem) return false; 173 | 174 | if (matcherItem.action !== monitorItem.action) { 175 | return false; 176 | } 177 | 178 | if (SC.typeOf(matcherItem.state) === "instance" && matcherItem.state === monitorItem.state) { 179 | return true; 180 | } 181 | 182 | if (matcherItem.state === monitorItem.state.get('stateName')) { 183 | return true; 184 | } 185 | 186 | return false; 187 | } 188 | 189 | }); -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/ext.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore Statechart 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | require('sproutcore-statechart/ext/function'); 9 | -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/ext/function.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SC.Statechart - A Statechart Framework for SproutCore 3 | // Copyright: ©2010, 2011 Michael Cohen, and contributors. 4 | // Portions @2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | /*globals SC */ 9 | 10 | if (SC.EXTEND_PROTOTYPES) { 11 | 12 | /** 13 | Extends the JS Function object with the handleActions method that 14 | will provide more advanced action handling capabilities when constructing 15 | your statechart's states. 16 | 17 | By default, when you add a method to a state, the state will react to 18 | actions that matches a method's name, like so: 19 | 20 | {{{ 21 | 22 | state = SC.State.extend({ 23 | 24 | // Will be invoked when a action named "foo" is sent to this state 25 | foo: function(action, sender, context) { ... } 26 | 27 | }) 28 | 29 | }}} 30 | 31 | In some situations, it may be advantageous to use one method that can react to 32 | multiple actions instead of having multiple methods that essentially all do the 33 | same thing. In order to set a method to handle more than one action you use 34 | the handleActions method which can be supplied a list of string and/or regular 35 | expressions. The following example demonstrates the use of handleActions: 36 | 37 | {{{ 38 | 39 | state = SC.State.extend({ 40 | 41 | actionHandlerA: function(action, sender, context) { 42 | 43 | }.handleActions('foo', 'bar'), 44 | 45 | actionHandlerB: function(action, sender, context) { 46 | 47 | }.handleActions(/num\d/, 'decimal') 48 | 49 | }) 50 | 51 | }}} 52 | 53 | Whenever actions 'foo' and 'bar' are sent to the state, the method actionHandlerA 54 | will be invoked. When there is an action that matches the regular expression 55 | /num\d/ or the action is 'decimal' then actionHandlerB is invoked. In both 56 | cases, the name of the action will be supplied to the action handler. 57 | 58 | It should be noted that the use of regular expressions may impact performance 59 | since that statechart will not be able to fully optimize the action handling logic based 60 | on its use. Therefore the use of regular expression should be used sparingly. 61 | 62 | @param {(String|RegExp)...} args 63 | */ 64 | Function.prototype.handleActions = function() { 65 | var args = Array.prototype.slice.call(arguments); 66 | args.unshift(this); 67 | return SC.handleActions.apply(SC, args); 68 | }; 69 | 70 | /** 71 | Extends the JS Function object with the stateObserves method that will 72 | create a state observe handler on a given state object. 73 | 74 | Use a stateObserves() instead of the common observes() method when you want a 75 | state to observer changes to some property on the state itself or some other 76 | object. 77 | 78 | Any method on the state that has stateObserves is considered a state observe 79 | handler and behaves just like when you use observes() on a method, but with an 80 | important difference. When you apply stateObserves to a method on a state, those 81 | methods will be active *only* when the state is entered, otherwise those methods 82 | will be inactive. This removes the need for you having to explicitly call 83 | addObserver and removeObserver. As an example: 84 | 85 | {{{ 86 | 87 | state = SC.State.extend({ 88 | 89 | foo: null, 90 | 91 | user: null, 92 | 93 | observeHandlerA: function(target, key) { 94 | 95 | }.stateObserves('MyApp.someController.status'), 96 | 97 | observeHandlerB: function(target, key) { 98 | 99 | }.stateObserves('foo'), 100 | 101 | observeHandlerC: function(target, key) { 102 | 103 | }.stateObserves('.user.name', '.user.salary') 104 | 105 | }) 106 | 107 | }}} 108 | 109 | Above, state has three state observe handlers: observeHandlerA, observeHandlerB, and 110 | observeHandlerC. When state is entered, the state will automatically add itself as 111 | an observer for all of its registered state observe handlers. Therefore when 112 | foo changes, observeHandlerB will be invoked, and when MyApp.someController's status 113 | changes then observeHandlerA will be invoked. The moment that state is exited then 114 | the state will automatically remove itself as an observer for all of its registered 115 | state observe handlers. Therefore none of the state observe handlers will be 116 | invoked until the next time the state is entered. 117 | 118 | @param {String...} args 119 | */ 120 | Function.prototype.stateObserves = function() { 121 | var args = Array.prototype.slice.call(arguments); 122 | args.unshift(this); 123 | return SC.stateObserves.apply(SC, args); 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/main.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore Runtime 3 | // Copyright: ©2011 Strobe Inc. and contributors. 4 | // License: Licensed under MIT license (see license.js) 5 | // ========================================================================== 6 | 7 | require('sproutcore-statechart/core'); 8 | require('sproutcore-statechart/debug'); 9 | require('sproutcore-statechart/ext'); 10 | require('sproutcore-statechart/system'); 11 | -------------------------------------------------------------------------------- /packages/sproutcore-statechart/lib/mixins/delegate_support.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore - JavaScript Application Framework 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | /** 9 | @namespace 10 | 11 | Support methods for the Delegate design pattern. 12 | 13 | The Delegate design pattern makes it easy to delegate a portion of your 14 | application logic to another object. This is most often used in views to 15 | delegate various application-logic decisions to controllers in order to 16 | avoid having to bake application-logic directly into the view itself. 17 | 18 | The methods provided by this mixin make it easier to implement this pattern 19 | but they are not required to support delegates. 20 | 21 | ## The Pattern 22 | 23 | The delegate design pattern typically means that you provide a property, 24 | usually ending in "delegate", that can be set to another object in the 25 | system. 26 | 27 | When events occur or logic decisions need to be made that you would prefer 28 | to delegate, you can call methods on the delegate if it is set. If the 29 | delegate is not set, you should provide some default functionality instead. 30 | 31 | Note that typically delegates are not observable, hence it is not necessary 32 | to use get() to retrieve the value of the delegate. 33 | 34 | @since SproutCore 1.0 35 | 36 | */ 37 | SC.DelegateSupport = { 38 | 39 | /** 40 | Selects the delegate that implements the specified method name. Pass one 41 | or more delegates. The receiver is automatically included as a default. 42 | 43 | This can be more efficient than using invokeDelegateMethod() which has 44 | to marshall arguments into a delegate call. 45 | 46 | @param {String} methodName 47 | @param {Object...} delegate one or more delegate arguments 48 | @returns {Object} delegate or null 49 | */ 50 | delegateFor: function(methodName) { 51 | var idx = 1, 52 | len = arguments.length, 53 | ret ; 54 | 55 | while(idx 0; 20 | }.property('testInvokedCount'), 21 | 22 | test: function(arg1, arg2) { 23 | this.set('testInvokedCount', this.get('testInvokedCount') + 1); 24 | this.set('arg1', arg1); 25 | this.set('arg2', arg2); 26 | if (this.get('returnValue') !== undefined) { 27 | return this.get('returnValue'); 28 | } 29 | } 30 | }); 31 | 32 | obj1 = SC.Object.extend(SC.StatechartManager, { 33 | 34 | initialState: 'stateA', 35 | 36 | rootStateExample: TestState.extend({ 37 | testX: function(arg1, arg2) { 38 | this.set('testInvokedCount', this.get('testInvokedCount') + 1); 39 | this.set('arg1', arg1); 40 | this.set('arg2', arg2); 41 | if (this.get('returnValue') !== undefined) { 42 | return this.get('returnValue'); 43 | } 44 | } 45 | }), 46 | 47 | stateA: TestState.extend(), 48 | 49 | stateB: TestState.extend() 50 | 51 | }); 52 | 53 | obj1 = obj1.create(); 54 | rootState1 = obj1.get('rootState'); 55 | stateA = obj1.getState('stateA'); 56 | stateB = obj1.getState('stateB'); 57 | 58 | obj2 = SC.Object.extend(SC.StatechartManager, { 59 | 60 | statesAreConcurrent: YES, 61 | 62 | rootStateExample: TestState.extend({ 63 | testX: function(arg1, arg2) { 64 | this.set('testInvokedCount', this.get('testInvokedCount') + 1); 65 | this.set('arg1', arg1); 66 | this.set('arg2', arg2); 67 | if (this.get('returnValue') !== undefined) { 68 | return this.get('returnValue'); 69 | } 70 | } 71 | }), 72 | 73 | stateC: TestState.extend(), 74 | 75 | stateD: TestState.extend() 76 | 77 | }); 78 | 79 | obj2 = obj2.create(); 80 | rootState2 = obj2.get('rootState'); 81 | stateC = obj2.getState('stateC'); 82 | stateD = obj2.getState('stateD'); 83 | }, 84 | 85 | teardown: function() { 86 | TestState = obj1 = rootState1 = stateA = stateB = null; 87 | obj2 = rootState2 = stateC = stateD = null; 88 | } 89 | }); 90 | 91 | test("check obj1 - invoke method test1", function() { 92 | var result = obj1.invokeStateMethod('test1'); 93 | ok(!rootState1.get('testInvoked'), "root state test method should not have been invoked"); 94 | ok(!stateA.get('testInvoked'), "state A's test method should not have been invoked"); 95 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 96 | }); 97 | 98 | test("check obj1 - invoke method test, current state A, no args, no return value", function() { 99 | var result = obj1.invokeStateMethod('test'); 100 | equals(stateA.get('testInvokedCount'), 1, "state A's test method should have been invoked once"); 101 | equals(stateA.get('arg1'), undefined, "state A's arg1 method should be undefined"); 102 | equals(stateA.get('arg2'), undefined, "state A's arg2 method should be undefined"); 103 | equals(result, undefined, "returned result should be undefined"); 104 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 105 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 106 | }); 107 | 108 | test("check obj1 - invoke method test, current state A, one args, no return value", function() { 109 | var result = obj1.invokeStateMethod('test', "frozen"); 110 | ok(stateA.get('testInvoked'), "state A's test method should have been invoked"); 111 | equals(stateA.get('arg1'), "frozen", "state A's arg1 method should be 'frozen'"); 112 | equals(stateA.get('arg2'), undefined, "state A's arg2 method should be undefined"); 113 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 114 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 115 | }); 116 | 117 | test("check obj1 - invoke method test, current state A, two args, no return value", function() { 118 | var result = obj1.invokeStateMethod('test', 'frozen', 'canuck'); 119 | ok(stateA.get('testInvoked'), "state A's test method should have been invoked"); 120 | equals(stateA.get('arg1'), "frozen", "state A's arg1 method should be 'frozen'"); 121 | equals(stateA.get('arg2'), "canuck", "state A's arg2 method should be undefined"); 122 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 123 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 124 | }); 125 | 126 | test("check obj1 - invoke method test, current state A, no args, return value", function() { 127 | stateA.set('returnValue', 100); 128 | var result = obj1.invokeStateMethod('test'); 129 | ok(stateA.get('testInvoked'), "state A's test method should have been invoked"); 130 | equals(result, 100, "returned result should be 100"); 131 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 132 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 133 | }); 134 | 135 | test("check obj1 - invoke method test, current state B, two args, return value", function() { 136 | stateB.set('returnValue', 100); 137 | obj1.gotoState(stateB); 138 | ok(stateB.get('isCurrentState'), "state B should be curren state"); 139 | var result = obj1.invokeStateMethod('test', 'frozen', 'canuck'); 140 | ok(!stateA.get('testInvoked'), "state A's test method should not have been invoked"); 141 | equals(stateB.get('testInvokedCount'), 1, "state B's test method should have been invoked once"); 142 | equals(stateB.get('arg1'), 'frozen', "state B's arg1 method should be 'frozen'"); 143 | equals(stateB.get('arg2'), 'canuck', "state B's arg2 method should be 'canuck'"); 144 | equals(result, 100, "returned result should be 100"); 145 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 146 | }); 147 | 148 | test("check obj1 - invoke method test, current state A, use callback", function() { 149 | var callbackState, callbackResult; 150 | obj1.invokeStateMethod('test', function(state, result) { 151 | callbackState = state; 152 | callbackResult = result; 153 | }); 154 | ok(stateA.get('testInvoked'), "state A's test method should have been invoked"); 155 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 156 | equals(callbackState, stateA, "state should be state A"); 157 | equals(callbackResult, undefined, "result should be undefined"); 158 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 159 | }); 160 | 161 | test("check obj1- invoke method test, current state B, use callback", function() { 162 | var callbackState, callbackResult; 163 | obj1.gotoState(stateB); 164 | stateB.set('returnValue', 100); 165 | obj1.invokeStateMethod('test', function(state, result) { 166 | callbackState = state; 167 | callbackResult = result; 168 | }); 169 | ok(!stateA.get('testInvoked'), "state A's test method should not have been invoked"); 170 | ok(stateB.get('testInvoked'), "state B's test method should have been invoked"); 171 | equals(callbackState, stateB, "state should be state B"); 172 | equals(callbackResult, 100, "result should be 100"); 173 | ok(!rootState1.get('testInvoked'), "root state's test method should not have been invoked"); 174 | }); 175 | 176 | test("check obj1 - invoke method testX", function() { 177 | rootState1.set('returnValue', 100); 178 | var result = obj1.invokeStateMethod('testX'); 179 | equals(rootState1.get('testInvokedCount'), 1, "root state's testX method should not have been invoked once"); 180 | equals(result, 100, "result should have value 100"); 181 | ok(!stateA.get('testInvoked'), "state A's test method should have been invoked"); 182 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 183 | }); 184 | 185 | test("check obj2 - invoke method test1", function() { 186 | var result = obj2.invokeStateMethod('test1'); 187 | ok(!rootState2.get('testInvoked'), "root state test method should not have been invoked"); 188 | ok(!stateC.get('testInvoked'), "state A's test method should not have been invoked"); 189 | ok(!stateD.get('testInvoked'), "state B's test method should not have been invoked"); 190 | }); 191 | 192 | test("check obj2 - invoke test, no args, no return value", function() { 193 | var result = obj2.invokeStateMethod('test'); 194 | equals(stateC.get('testInvokedCount'), 1, "state C's test method should have been invoked once"); 195 | equals(stateD.get('testInvokedCount'), 1, "state D's test method should have been invoked once"); 196 | ok(!rootState1.get('testInvoked'), "root state test method should not have been invoked"); 197 | equals(stateC.get('arg1'), undefined, "state C's arg1 method should be undefined"); 198 | equals(stateC.get('arg2'), undefined, "state C's arg2 method should be undefined"); 199 | equals(stateD.get('arg1'), undefined, "state D's arg1 method should be undefined"); 200 | equals(stateD.get('arg2'), undefined, "state D's arg2 method should be undefined"); 201 | equals(result, undefined, "returned result should be undefined"); 202 | }); 203 | 204 | test("check obj2 - invoke test, two args, return value, callback", function() { 205 | var numCallbacks = 0, 206 | callbackInfo = {}; 207 | stateC.set('returnValue', 100); 208 | stateD.set('returnValue', 200); 209 | var result = obj2.invokeStateMethod('test', 'frozen', 'canuck', function(state, result) { 210 | numCallbacks += 1; 211 | callbackInfo['state' + numCallbacks] = state; 212 | callbackInfo['result' + numCallbacks] = result; 213 | }); 214 | 215 | ok(!rootState1.get('testInvoked'), "root state test method should not have been invoked"); 216 | equals(stateC.get('testInvokedCount'), 1, "state C's test method should have been invoked once"); 217 | equals(stateD.get('testInvokedCount'), 1, "state D's test method should have been invoked once"); 218 | 219 | equals(stateC.get('arg1'), 'frozen', "state C's arg1 method should be 'frozen'"); 220 | equals(stateC.get('arg2'), 'canuck', "state C's arg2 method should be 'canuck'"); 221 | 222 | equals(stateD.get('arg1'), 'frozen', "state D's arg1 method should be 'frozen'"); 223 | equals(stateD.get('arg2'), 'canuck', "state D's arg2 method should be 'canuck'"); 224 | 225 | equals(numCallbacks, 2, "callback should have been invoked twice"); 226 | equals(callbackInfo['state1'], stateC, "first callback state arg should be state C"); 227 | equals(callbackInfo['result1'], 100, "first callback result arg should be 100"); 228 | equals(callbackInfo['state2'], stateD, "second callback state arg should be state D"); 229 | equals(callbackInfo['result2'], 200, "second callback result arg should be 200"); 230 | 231 | equals(result, undefined, "returned result should be undefined"); 232 | }); 233 | 234 | test("check obj2 - invoke method testX", function() { 235 | rootState2.set('returnValue', 100); 236 | var result = obj2.invokeStateMethod('testX'); 237 | equals(rootState2.get('testInvokedCount'), 1, "root state's testX method should not have been invoked once"); 238 | equals(result, 100, "result should have value 100"); 239 | ok(!stateA.get('testInvoked'), "state A's test method should have been invoked"); 240 | ok(!stateB.get('testInvoked'), "state B's test method should not have been invoked"); 241 | }); -------------------------------------------------------------------------------- /packages/sproutcore-statechart/tests/statechart/owner_test.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // SC.Statechart Unit Test 3 | // ========================================================================== 4 | /*globals SC statechart State */ 5 | 6 | var obj1, rootState1, stateA, stateB, stateX, stateY, stateZ; 7 | var obj2, rootState2, stateC, stateD; 8 | var obj3, rootState3, stateE, stateF; 9 | var owner, owner2; 10 | var TestObject, TestState; 11 | 12 | module("SC.Statechart: Change Statechart Owner Property Tests", { 13 | setup: function() { 14 | owner = SC.Object.create({ 15 | toString: function() { return "owner"; } 16 | }); 17 | 18 | owner2 = SC.Object.create({ 19 | toString: function() { return "owner2"; } 20 | }); 21 | 22 | TestState = SC.State.extend({ 23 | accessedOwner: null, 24 | 25 | reset: function() { 26 | this.set('accessedOwner', null); 27 | }, 28 | 29 | render: function() { 30 | this.set('accessedOwner', this.get('owner')); 31 | } 32 | }); 33 | 34 | TestObject = SC.Object.extend(SC.StatechartManager, { 35 | render: function() { 36 | this.invokeStateMethod('render'); 37 | } 38 | }); 39 | 40 | obj1 = TestObject.extend({ 41 | 42 | initialState: 'stateA', 43 | 44 | stateA: TestState.extend({ 45 | foo: function() { 46 | this.gotoState('stateB'); 47 | } 48 | }), 49 | 50 | stateB: TestState.extend({ 51 | bar: function() { 52 | this.gotoState('stateA'); 53 | } 54 | }), 55 | 56 | stateX: TestState.extend({ 57 | initialSubstate: 'stateY', 58 | 59 | stateY: TestState.extend({ 60 | initialSubstate: 'stateZ', 61 | 62 | stateZ: TestState.extend() 63 | }) 64 | }) 65 | 66 | }); 67 | 68 | obj1 = obj1.create(); 69 | rootState1 = obj1.get('rootState'); 70 | stateA = obj1.getState('stateA'); 71 | stateB = obj1.getState('stateB'); 72 | stateX = obj1.getState('stateX'); 73 | stateY = obj1.getState('stateY'); 74 | stateZ = obj1.getState('stateZ'); 75 | 76 | obj2 = TestObject.extend({ 77 | 78 | owner: owner, 79 | 80 | initialState: 'stateC', 81 | 82 | stateC: TestState.extend({ 83 | foo: function() { 84 | this.gotoState('stateD'); 85 | } 86 | }), 87 | 88 | stateD: TestState.extend({ 89 | bar: function() { 90 | this.gotoState('stateC'); 91 | } 92 | }) 93 | 94 | }); 95 | 96 | obj2 = obj2.create(); 97 | rootState2 = obj2.get('rootState'); 98 | stateC = obj2.getState('stateC'); 99 | stateD = obj2.getState('stateD'); 100 | 101 | obj3 = TestObject.extend({ 102 | 103 | statechartOwnerKey: 'fooOwner', 104 | 105 | fooOwner: owner, 106 | 107 | initialState: 'stateE', 108 | 109 | stateE: TestState.extend({ 110 | foo: function() { 111 | this.gotoState('stateF'); 112 | } 113 | }), 114 | 115 | stateF: TestState.extend({ 116 | bar: function() { 117 | this.gotoState('stateE'); 118 | } 119 | }) 120 | 121 | }); 122 | 123 | obj3 = obj3.create(); 124 | rootState3 = obj3.get('rootState'); 125 | stateE = obj3.getState('stateE'); 126 | stateF = obj3.getState('stateF'); 127 | }, 128 | 129 | teardown: function() { 130 | obj1 = rootState1 = stateA = stateB = stateX = stateY = stateZ = null; 131 | obj2 = rootState2 = stateC = stateD = null; 132 | obj3 = rootState3 = stateE = stateF = null; 133 | owner = owner2 = null; 134 | TestObject = TestState = null; 135 | } 136 | }); 137 | 138 | test("check obj1 -- basic owner get and set", function() { 139 | equals(rootState1.get('owner'), obj1, "root state's owner should be obj"); 140 | equals(stateA.get('owner'), obj1, "state A's owner should be obj"); 141 | equals(stateB.get('owner'), obj1, "state B's owner should be obj"); 142 | equals(stateX.get('owner'), obj1, "state X's owner should be obj"); 143 | equals(stateY.get('owner'), obj1, "state Y's owner should be obj"); 144 | equals(stateZ.get('owner'), obj1, "state Z's owner should be obj"); 145 | 146 | obj1.set('owner', owner); 147 | 148 | equals(rootState1.get('owner'), owner, "root state's owner should be owner"); 149 | equals(stateA.get('owner'), owner, "state A's owner should be owner"); 150 | equals(stateB.get('owner'), owner, "state B's owner should be owner"); 151 | equals(stateX.get('owner'), owner, "state X's owner should be owner"); 152 | equals(stateY.get('owner'), owner, "state Y's owner should be owner"); 153 | equals(stateZ.get('owner'), owner, "state Z's owner should be owner"); 154 | 155 | obj1.set('owner', null); 156 | 157 | equals(rootState1.get('owner'), obj1, "root state's owner should be obj"); 158 | equals(stateA.get('owner'), obj1, "state A's owner should be obj"); 159 | equals(stateB.get('owner'), obj1, "state B's owner should be obj"); 160 | equals(stateX.get('owner'), obj1, "state X's owner should be obj"); 161 | equals(stateY.get('owner'), obj1, "state Y's owner should be obj"); 162 | equals(stateZ.get('owner'), obj1, "state Z's owner should be obj"); 163 | }); 164 | 165 | test("check stateA -- access owner via invokeStateMethod", function() { 166 | ok(stateA.get('isCurrentState')); 167 | equals(stateA.get('accessedOwner'), null); 168 | 169 | obj1.render(); 170 | 171 | equals(stateA.get('accessedOwner'), obj1); 172 | 173 | stateA.reset(); 174 | obj1.set('owner', owner); 175 | obj1.render(); 176 | 177 | equals(stateA.get('accessedOwner'), owner); 178 | }); 179 | 180 | test("check stateZ -- access owner via invokeStateMethod", function() { 181 | obj1.gotoState(stateZ); 182 | ok(stateZ.get('isCurrentState')); 183 | 184 | equals(stateZ.get('accessedOwner'), null); 185 | 186 | obj1.render(); 187 | 188 | equals(stateZ.get('accessedOwner'), obj1); 189 | 190 | stateA.reset(); 191 | obj1.set('owner', owner); 192 | obj1.render(); 193 | 194 | equals(stateZ.get('accessedOwner'), owner); 195 | }); 196 | 197 | test("check obj2 -- statechartOwnerKey", function() { 198 | equals(rootState2.get('owner'), owner, "root state's owner should be owner"); 199 | equals(stateC.get('owner'), owner, "state C's owner should be owner"); 200 | equals(stateD.get('owner'), owner, "state D's owner should be owner"); 201 | 202 | obj2.set('owner', null); 203 | 204 | equals(rootState2.get('owner'), obj2, "root state's owner should be obj"); 205 | equals(stateC.get('owner'), obj2, "state C's owner should be obj"); 206 | equals(stateD.get('owner'), obj2, "state D's owner should be obj"); 207 | }); 208 | 209 | test("check obj3 -- basic owner get and set", function() { 210 | equals(obj3.get('statechartOwnerKey'), 'fooOwner', "obj's statechartOwnerKey should be fooOwner"); 211 | equals(obj3.get('fooOwner'), owner, "obj's fooOwner should be owner"); 212 | 213 | equals(rootState3.get('owner'), owner, "root state's owner should be owner"); 214 | equals(stateE.get('owner'), owner, "state E's owner should be owner"); 215 | equals(stateF.get('owner'), owner, "state F's owner should be owner"); 216 | 217 | obj3.set('fooOwner', null); 218 | 219 | equals(rootState3.get('owner'), obj3, "root state's owner should be obj"); 220 | equals(stateE.get('owner'), obj3, "state E's owner should be obj"); 221 | equals(stateF.get('owner'), obj3, "state F's owner should be obj"); 222 | 223 | obj3.set('fooOwner', owner2); 224 | 225 | equals(rootState3.get('owner'), owner2, "root state's owner2 should be owner2"); 226 | equals(stateE.get('owner'), owner2, "state E's owner2 should be owner2"); 227 | equals(stateF.get('owner'), owner2, "state F's owner2 should be owner2"); 228 | 229 | ok(obj3.hasObserverFor('fooOwner')); 230 | equals(rootState3.get('owner'), owner2); 231 | 232 | obj3.destroy(); 233 | 234 | ok(!obj3.hasObserverFor('fooOwner')); 235 | equals(rootState3.get('owner'), null); 236 | }); -------------------------------------------------------------------------------- /packages/sproutcore-statechart/tests/system/state_route_handler_context/methods/retry.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // SC.State Unit Test 3 | // ========================================================================== 4 | /*globals SC externalState1 externalState2 */ 5 | 6 | var state, params, context; 7 | 8 | module("SC.StateRouteHandlerContext: retry Method Tests", { 9 | 10 | setup: function() { 11 | 12 | params = { }; 13 | 14 | state = SC.Object.create({ 15 | 16 | info: {}, 17 | 18 | handler: function(params) { 19 | this.info.handler = { 20 | params: params 21 | }; 22 | } 23 | 24 | }); 25 | 26 | context = SC.StateRouteHandlerContext.create({ 27 | 28 | state: state, 29 | 30 | params: params 31 | 32 | }); 33 | 34 | }, 35 | 36 | teardown: function() { 37 | params = state = context = null; 38 | } 39 | 40 | }); 41 | 42 | test("Invoke retry with context's handler property assigned a function value", function() { 43 | 44 | context.set('handler', state.handler); 45 | context.retry(); 46 | 47 | var info = state.info; 48 | 49 | ok(info.handler, "state's handler method was invoked"); 50 | equals(info.handler.params, params, "state's handler was provided params"); 51 | 52 | }); 53 | 54 | test("Invoke retry with context's handler property assigned a string value", function() { 55 | 56 | context.set('handler', 'handler'); 57 | context.retry(); 58 | 59 | var info = state.info; 60 | 61 | ok(info.handler, "state's handler method was invoked"); 62 | equals(info.handler.params, params, "state's handler was provided params"); 63 | 64 | }); -------------------------------------------------------------------------------- /packages/sproutcore-utils/lib/main.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | require('sproutcore-utils/responds_to'); 9 | require('sproutcore-utils/try_to_perform'); -------------------------------------------------------------------------------- /packages/sproutcore-utils/lib/responds_to.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | var slice = Array.prototype.slice; 9 | 10 | var respondsTo = function(obj, methodName) { 11 | return !!(obj[methodName] instanceof Function); 12 | }; 13 | 14 | /** 15 | @param {Object} obj The object to check for the method 16 | @param {String} methodName The method name to check for 17 | */ 18 | SC.respondsTo = respondsTo; 19 | 20 | SC.Object.reopen( 21 | /** @scope SC.Object.prototype */{ 22 | 23 | respondsTo: function() { 24 | var args = slice.call(arguments); 25 | args.unshift(this); 26 | return SC.respondsTo.apply(SC, args); 27 | } 28 | 29 | }); -------------------------------------------------------------------------------- /packages/sproutcore-utils/lib/try_to_perform.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: SproutCore 3 | // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 | // Portions ©2008-2011 Apple Inc. All rights reserved. 5 | // License: Licensed under MIT license (see license.js) 6 | // ========================================================================== 7 | 8 | require('sproutcore-utils/responds_to'); 9 | 10 | var slice = Array.prototype.slice; 11 | 12 | var tryToPerform = function(obj, methodName) { 13 | var args = slice.call(arguments); 14 | args.shift(); args.shift(); 15 | return SC.respondsTo(obj, methodName) && (obj[methodName].apply(obj, args) !== false); 16 | }; 17 | 18 | /** 19 | Checks to see if the `methodName` exists on the `obj`, 20 | and if it does, invokes it with the arguments passed. 21 | 22 | @function 23 | 24 | @param {Object} obj The object to check for the method 25 | @param {String} methodName The method name to check for 26 | @param {Object...} args The arguments to pass to the method 27 | 28 | @returns {Boolean} true if the method does not return false 29 | @returns {Boolean} false otherwise 30 | */ 31 | SC.tryToPerform = tryToPerform; 32 | 33 | SC.Object.reopen( 34 | /** @scope SC.Object.prototype */{ 35 | 36 | tryToPerform: function() { 37 | var args = slice.call(arguments); 38 | args.unshift(this); 39 | return SC.tryToPerform.apply(SC, args); 40 | } 41 | 42 | }); -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Test Suite 6 | 7 | 8 | 9 | 10 |

QUnit Test Suite

11 |

12 |
13 |

14 |
    15 |
    test markup
    16 | 17 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/minispade.js: -------------------------------------------------------------------------------- 1 | // This is based on minispade but is modified 2 | 3 | if (typeof document !== "undefined") { 4 | (function() { 5 | minispade = { 6 | modules: {}, 7 | loaded: {}, 8 | 9 | require: function(name) { 10 | var loaded = minispade.loaded[name]; 11 | var mod = minispade.modules[name]; 12 | 13 | if (!loaded) { 14 | if (mod) { 15 | minispade.loaded[name] = true; 16 | mod(); 17 | } else { 18 | throw "The module '" + name + "' could not be found"; 19 | } 20 | } 21 | 22 | return loaded; 23 | }, 24 | 25 | register: function(name, callback) { 26 | minispade.modules[name] = callback; 27 | } 28 | }; 29 | })(); 30 | } 31 | -------------------------------------------------------------------------------- /tests/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.3.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | * Pulled Live from Git Fri Dec 9 20:50:01 UTC 2011 10 | * Last Commit: 0712230bb203c262211649b32bd712ec7df5f857 11 | */ 12 | 13 | /** Font Family and Sizes */ 14 | 15 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 16 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 20 | #qunit-tests { font-size: smaller; } 21 | 22 | 23 | /** Resets */ 24 | 25 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | 31 | /** Header */ 32 | 33 | #qunit-header { 34 | padding: 0.5em 0 0.5em 1em; 35 | 36 | color: #8699a4; 37 | background-color: #0d3349; 38 | 39 | font-size: 1.5em; 40 | line-height: 1em; 41 | font-weight: normal; 42 | 43 | border-radius: 15px 15px 0 0; 44 | -moz-border-radius: 15px 15px 0 0; 45 | -webkit-border-top-right-radius: 15px; 46 | -webkit-border-top-left-radius: 15px; 47 | } 48 | 49 | #qunit-header a { 50 | text-decoration: none; 51 | color: #c2ccd1; 52 | } 53 | 54 | #qunit-header a:hover, 55 | #qunit-header a:focus { 56 | color: #fff; 57 | } 58 | 59 | #qunit-banner { 60 | height: 5px; 61 | } 62 | 63 | #qunit-testrunner-toolbar { 64 | padding: 0.5em 0 0.5em 2em; 65 | color: #5E740B; 66 | background-color: #eee; 67 | } 68 | 69 | #qunit-userAgent { 70 | padding: 0.5em 0 0.5em 2.5em; 71 | background-color: #2b81af; 72 | color: #fff; 73 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 74 | } 75 | 76 | 77 | /** Tests: Pass/Fail */ 78 | 79 | #qunit-tests { 80 | list-style-position: inside; 81 | } 82 | 83 | #qunit-tests li { 84 | padding: 0.4em 0.5em 0.4em 2.5em; 85 | border-bottom: 1px solid #fff; 86 | list-style-position: inside; 87 | } 88 | 89 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 90 | display: none; 91 | } 92 | 93 | #qunit-tests li strong { 94 | cursor: pointer; 95 | } 96 | 97 | #qunit-tests li a { 98 | padding: 0.5em; 99 | color: #c2ccd1; 100 | text-decoration: none; 101 | } 102 | #qunit-tests li a:hover, 103 | #qunit-tests li a:focus { 104 | color: #000; 105 | } 106 | 107 | #qunit-tests ol { 108 | margin-top: 0.5em; 109 | padding: 0.5em; 110 | 111 | background-color: #fff; 112 | 113 | border-radius: 15px; 114 | -moz-border-radius: 15px; 115 | -webkit-border-radius: 15px; 116 | 117 | box-shadow: inset 0px 2px 13px #999; 118 | -moz-box-shadow: inset 0px 2px 13px #999; 119 | -webkit-box-shadow: inset 0px 2px 13px #999; 120 | } 121 | 122 | #qunit-tests table { 123 | border-collapse: collapse; 124 | margin-top: .2em; 125 | } 126 | 127 | #qunit-tests th { 128 | text-align: right; 129 | vertical-align: top; 130 | padding: 0 .5em 0 0; 131 | } 132 | 133 | #qunit-tests td { 134 | vertical-align: top; 135 | } 136 | 137 | #qunit-tests pre { 138 | margin: 0; 139 | white-space: pre-wrap; 140 | word-wrap: break-word; 141 | } 142 | 143 | #qunit-tests del { 144 | background-color: #e0f2be; 145 | color: #374e0c; 146 | text-decoration: none; 147 | } 148 | 149 | #qunit-tests ins { 150 | background-color: #ffcaca; 151 | color: #500; 152 | text-decoration: none; 153 | } 154 | 155 | /*** Test Counts */ 156 | 157 | #qunit-tests b.counts { color: black; } 158 | #qunit-tests b.passed { color: #5E740B; } 159 | #qunit-tests b.failed { color: #710909; } 160 | 161 | #qunit-tests li li { 162 | margin: 0.5em; 163 | padding: 0.4em 0.5em 0.4em 0.5em; 164 | background-color: #fff; 165 | border-bottom: none; 166 | list-style-position: inside; 167 | } 168 | 169 | /*** Passing Styles */ 170 | 171 | #qunit-tests li li.pass { 172 | color: #5E740B; 173 | background-color: #fff; 174 | border-left: 26px solid #C6E746; 175 | } 176 | 177 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 178 | #qunit-tests .pass .test-name { color: #366097; } 179 | 180 | #qunit-tests .pass .test-actual, 181 | #qunit-tests .pass .test-expected { color: #999999; } 182 | 183 | #qunit-banner.qunit-pass { background-color: #C6E746; } 184 | 185 | /*** Failing Styles */ 186 | 187 | #qunit-tests li li.fail { 188 | color: #710909; 189 | background-color: #fff; 190 | border-left: 26px solid #EE5757; 191 | white-space: pre; 192 | } 193 | 194 | #qunit-tests > li:last-child { 195 | border-radius: 0 0 15px 15px; 196 | -moz-border-radius: 0 0 15px 15px; 197 | -webkit-border-bottom-right-radius: 15px; 198 | -webkit-border-bottom-left-radius: 15px; 199 | } 200 | 201 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 202 | #qunit-tests .fail .test-name, 203 | #qunit-tests .fail .module-name { color: #000000; } 204 | 205 | #qunit-tests .fail .test-actual { color: #EE5757; } 206 | #qunit-tests .fail .test-expected { color: green; } 207 | 208 | #qunit-banner.qunit-fail { background-color: #EE5757; } 209 | 210 | 211 | /** Result */ 212 | 213 | #qunit-testresult { 214 | padding: 0.5em 0.5em 0.5em 2.5em; 215 | 216 | color: #2b81af; 217 | background-color: #D2E0E6; 218 | 219 | border-bottom: 1px solid white; 220 | } 221 | 222 | /** Fixture */ 223 | 224 | #qunit-fixture { 225 | position: absolute; 226 | top: -10000px; 227 | left: -10000px; 228 | } 229 | -------------------------------------------------------------------------------- /tests/qunit/run-qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wait until the test condition is true or a timeout occurs. Useful for waiting 3 | * on a server response or for a ui change (fadeIn, etc.) to occur. 4 | * 5 | * @param testFx javascript condition that evaluates to a boolean, 6 | * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or 7 | * as a callback function. 8 | * @param onReady what to do when testFx condition is fulfilled, 9 | * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or 10 | * as a callback function. 11 | * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used. 12 | */ 13 | 14 | function waitFor(testFx, onReady, timeOutMillis) { 15 | var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 30001, 16 | //< Default Max Timout is 3s, just kidding it's 30s 17 | start = new Date().getTime(), 18 | condition = false, 19 | interval = setInterval(function() { 20 | if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) { 21 | // If not time-out yet and condition not yet fulfilled 22 | condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code 23 | } else { 24 | if (!condition) { 25 | // If condition still not fulfilled (timeout but condition is 'false') 26 | console.log("'waitFor()' timeout"); 27 | phantom.exit(1); 28 | } else { 29 | // Condition fulfilled (timeout and/or condition is 'true') 30 | console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms."); 31 | typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled 32 | clearInterval(interval); //< Stop this interval 33 | } 34 | } 35 | }, 100); //< repeat check every 250ms 36 | }; 37 | 38 | 39 | if (phantom.args.length === 0 || phantom.args.length > 2) { 40 | console.log('Usage: run-qunit.js URL'); 41 | phantom.exit(); 42 | } 43 | 44 | var page = new WebPage(); 45 | 46 | // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") 47 | // hide these warnings ... 48 | 49 | page.onConsoleMessage = function(msg) { 50 | // annoying warnings are annoying 51 | if (msg.indexOf('is deprecated') < 0) { 52 | console.log(msg); 53 | } 54 | }; 55 | 56 | page.open(phantom.args[0], function(status) { 57 | if (status !== "success") { 58 | console.log("Unable to access network"); 59 | phantom.exit(); 60 | } else { 61 | waitFor(function() { 62 | return page.evaluate(function() { 63 | var el = document.getElementById('qunit-testresult'); 64 | if (el && el.innerText.match('completed')) { 65 | return true; 66 | } 67 | return false; 68 | }); 69 | }, function() { 70 | var failedNum = page.evaluate(function() { 71 | var el = document.getElementById('qunit-testresult'); 72 | console.log(el.innerText); 73 | try { 74 | return el.getElementsByClassName('failed')[0].innerHTML; 75 | } catch (e) {} 76 | return 10000; 77 | }); 78 | phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0); 79 | }); 80 | } 81 | }); 82 | --------------------------------------------------------------------------------