opal-spec 0.0.1 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -1,8 +1,7 @@
1
1
  .DS_Store
2
2
  doc
3
3
  /pkg
4
- /tmp
5
- .yardoc
6
- extras
7
- /opal-test*.js
8
-
4
+ /*.js
5
+ /*.gem
6
+ /build
7
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'opal'
6
+ gem 'sprockets'
data/README.md CHANGED
@@ -1,4 +1,91 @@
1
- OpalSpec
2
- =========
1
+ # opal-spec
3
2
 
4
- Spec framework for opal.
3
+ opal-spec is a minimal spec lib for opal, inspired by RSpec and MSpec.
4
+ It is designed to run on [opal](http://opalrb.org), and provides the
5
+ bare minimum to get specs running.
6
+
7
+ ## Writing Specs
8
+
9
+ Your specs should go into the `/spec` directory of your app. They take
10
+ the same form as rspec/mspec:
11
+
12
+ ```ruby
13
+ describe MyClass do
14
+ it 'should do some feature' do
15
+ 1.should == 1
16
+ end
17
+
18
+ it 'does something else' do
19
+ nil.should be_nil
20
+ false.should be_false
21
+ true.should be_true
22
+ end
23
+ end
24
+ ```
25
+
26
+ ### Running specs
27
+
28
+ Loading these specs in a browser won't just work. You need to run them.
29
+ The best place to do this is inside `spec/spec_helper.rb`, and with a
30
+ simple call:
31
+
32
+ ```ruby
33
+ Opal::Spec::Runner.autorun
34
+ ```
35
+
36
+ ### Async examples
37
+
38
+ Examples can be async, and need to be defined as so:
39
+
40
+ ```ruby
41
+ describe 'MyClass' do
42
+ # normal, sync example
43
+ it 'does something' do
44
+ # ...
45
+ end
46
+
47
+ # async example
48
+ async 'does something else too' do
49
+ # ...
50
+ end
51
+ end
52
+ ```
53
+
54
+ This just marks the example as being async. To actually handle the async
55
+ result, you also need to use a `run_async` call inside some future handler:
56
+
57
+ ```ruby
58
+ async 'HTTP requests should work' do
59
+ HTTP.get('users/1.json') do |response|
60
+ run_async {
61
+ response.ok?.should be_true
62
+ }
63
+ end
64
+ end
65
+ ```
66
+
67
+ The end of the block passed to `run_async` informs opal-spec that you are
68
+ done with this test, so it can move on.
69
+
70
+ Change Log
71
+ ----------
72
+
73
+ ### 0.2.7
74
+
75
+ * Can be built using asset pipeline/sprockets
76
+ * BrowserFormatter is now default
77
+
78
+ ### 0.0.3
79
+
80
+ * Allow group names to be non-strings
81
+ * Nested groups now have outer group name as prefix
82
+ * Nested groups should inherit `before` and `after` blocks
83
+
84
+ ### 0.0.2
85
+
86
+ * Added seperate BrowserFormatter class for cleaner output
87
+ * Update Rake tasks to use new Opal::Builder class
88
+
89
+ ### 0.0.1
90
+
91
+ Initial release
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ desc "Build opal-spec (with opal) into build"
5
+ task :build => [:dir] do
6
+ File.open('build/opal-spec.js', 'w+') do |out|
7
+ out.puts Opal.process('opal-spec')
8
+ end
9
+ end
10
+
11
+ desc "Build example specs ready to run"
12
+ task :build_specs => [:dir] do
13
+ Opal.append_path File.join(File.dirname(__FILE__), 'spec')
14
+
15
+ File.open('build/specs.js', 'w+') do |out|
16
+ out.puts Opal.process('specs')
17
+ end
18
+ end
19
+ task :dir do
20
+ FileUtils.mkdir_p 'build'
21
+ end
22
+
23
+ task :test do
24
+ Opal::Spec.runner
25
+ end
26
+
27
+ task :default => [:build_specs, :test]
@@ -0,0 +1,194 @@
1
+ module Opal
2
+ module Spec
3
+ class BrowserFormatter
4
+ CSS = <<-EOS
5
+
6
+ body {
7
+ font-size: 14px;
8
+ font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
9
+ }
10
+
11
+ pre {
12
+ font-family: "Bitstream Vera Sans Mono", Monaco, "Lucida Console", monospace;
13
+ font-size: 12px;
14
+ color: #444444;
15
+ white-space: pre;
16
+ padding: 3px 0px 3px 12px;
17
+ margin: 0px 0px 8px;
18
+
19
+ background: #FAFAFA;
20
+ -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
21
+ -webkit-border-radius: 3px;
22
+ -moz-border-radius: 3px;
23
+ border-radius: 3px;
24
+ border: 1px solid #DDDDDD;
25
+ }
26
+
27
+ ul.example_groups {
28
+ list-style-type: none;
29
+ }
30
+
31
+ li.group.passed .group_description {
32
+ color: #597800;
33
+ font-weight: bold;
34
+ }
35
+
36
+ li.group.failed .group_description {
37
+ color: #FF000E;
38
+ font-weight: bold;
39
+ }
40
+
41
+ li.example.passed {
42
+ color: #597800;
43
+ }
44
+
45
+ li.example.failed {
46
+ color: #FF000E;
47
+ }
48
+
49
+ .examples {
50
+ list-style-type: none;
51
+ }
52
+ EOS
53
+
54
+ def initialize
55
+ @examples = []
56
+ @failed_examples = []
57
+ end
58
+
59
+ def start
60
+ %x{
61
+ if (!document || !document.body) {
62
+ #{ raise "Not running in browser." };
63
+ }
64
+
65
+ var summary_element = document.createElement('p');
66
+ summary_element.className = 'summary';
67
+ summary_element.innerHTML = "Running...";
68
+
69
+ var groups_element = document.createElement('ul');
70
+ groups_element.className = 'example_groups';
71
+
72
+ var target = document.getElementById('opal-spec-output');
73
+
74
+ if (!target) {
75
+ target = document.body;
76
+ }
77
+
78
+ target.appendChild(summary_element);
79
+ target.appendChild(groups_element);
80
+
81
+ var styles = document.createElement('style');
82
+ styles.type = 'text/css';
83
+
84
+ if (styles.styleSheet) {
85
+ styles.styleSheet.cssText = __scope.CSS;
86
+ }
87
+ else {
88
+ styles.appendChild(document.createTextNode(__scope.CSS));
89
+ }
90
+
91
+ document.getElementsByTagName('head')[0].appendChild(styles);
92
+ }
93
+
94
+ @start_time = Time.now.to_f
95
+ @groups_element = `groups_element`
96
+ @summary_element = `summary_element`
97
+ end
98
+
99
+ def finish
100
+ time = Time.now.to_f - @start_time
101
+ text = "\n#{example_count} examples, #{@failed_examples.size} failures (time taken: #{time})"
102
+ `#{@summary_element}.innerHTML = text`
103
+ end
104
+
105
+ def example_group_started group
106
+ @example_group = group
107
+ @example_group_failed = false
108
+
109
+ %x{
110
+ var group_element = document.createElement('li');
111
+
112
+ var description = document.createElement('span');
113
+ description.className = 'group_description';
114
+ description.innerHTML = #{group.description.to_s};
115
+ group_element.appendChild(description);
116
+
117
+ var example_list = document.createElement('ul');
118
+ example_list.className = 'examples';
119
+ group_element.appendChild(example_list);
120
+
121
+ #@groups_element.appendChild(group_element);
122
+ }
123
+
124
+ @group_element = `group_element`
125
+ @example_list = `example_list`
126
+ end
127
+
128
+ def example_group_finished group
129
+ if @example_group_failed
130
+ `#@group_element.className = 'group failed';`
131
+ else
132
+ `#@group_element.className = 'group passed';`
133
+ end
134
+ end
135
+
136
+ def example_started example
137
+ @examples << example
138
+ @example = example
139
+ end
140
+
141
+ def example_failed example
142
+ @failed_examples << example
143
+ @example_group_failed = true
144
+
145
+ exception = example.exception
146
+
147
+ case exception
148
+ when Opal::Spec::ExpectationNotMetError
149
+ output = exception.message
150
+ else
151
+ output = "#{exception.class.name}: #{exception.message}\n"
152
+ output += " #{exception.backtrace.join "\n "}\n"
153
+ end
154
+
155
+ %x{
156
+ var wrapper = document.createElement('li');
157
+ wrapper.className = 'example failed';
158
+
159
+ var description = document.createElement('span');
160
+ description.className = 'example_description';
161
+ description.innerHTML = #{example.description};
162
+
163
+ var exception = document.createElement('pre');
164
+ exception.className = 'exception';
165
+ exception.innerHTML = output;
166
+
167
+ wrapper.appendChild(description);
168
+ wrapper.appendChild(exception);
169
+
170
+ #@example_list.appendChild(wrapper);
171
+ #@example_list.style.display = 'list-item';
172
+ }
173
+ end
174
+
175
+ def example_passed example
176
+ %x{
177
+ var wrapper = document.createElement('li');
178
+ wrapper.className = 'example passed';
179
+
180
+ var description = document.createElement('span');
181
+ description.className = 'example_description';
182
+ description.innerHTML = #{example.description};
183
+
184
+ wrapper.appendChild(description);
185
+ #@example_list.appendChild(wrapper);
186
+ }
187
+ end
188
+
189
+ def example_count
190
+ @examples.size
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,78 @@
1
+ module Opal
2
+ module Spec
3
+ class Example
4
+ attr_reader :description, :example_group, :exception
5
+ attr_accessor :asynchronous
6
+
7
+ def initialize(group, desc, block)
8
+ @example_group = group
9
+ @description = desc
10
+ @__block__ = block
11
+ end
12
+
13
+ def finish_running
14
+ if @exception
15
+ @example_group.example_failed self
16
+ else
17
+ @example_group.example_passed self
18
+ end
19
+ end
20
+
21
+ def run
22
+ begin
23
+ @example_group.example_started self
24
+ run_before_hooks
25
+ instance_eval(&@__block__)
26
+ rescue => e
27
+ @exception = e
28
+ ensure
29
+ run_after_hooks unless @asynchronous
30
+ end
31
+
32
+ if @asynchronous
33
+ # must wait ...
34
+ else
35
+ finish_running
36
+ end
37
+ end
38
+
39
+ def run_after_hooks
40
+ begin
41
+ @example_group.after_hooks.each do |after|
42
+ instance_eval &after
43
+ end
44
+ rescue => e
45
+ @exception = e
46
+ end
47
+ end
48
+
49
+ def run_before_hooks
50
+ @example_group.before_hooks.each do |before|
51
+ instance_eval &before
52
+ end
53
+ end
54
+
55
+ def run_async(&block)
56
+ begin
57
+ block.call
58
+ rescue => e
59
+ @exception = e
60
+ ensure
61
+ run_after_hooks
62
+ end
63
+
64
+ finish_running
65
+ end
66
+
67
+ def set_timeout(duration, &block)
68
+ %x{
69
+ setTimeout(function() {
70
+ #{ block.call };
71
+ }, duration);
72
+ }
73
+
74
+ self
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,94 @@
1
+ module Opal
2
+ module Spec
3
+ class ExampleGroup
4
+ @example_groups = []
5
+ def self.example_groups
6
+ @example_groups
7
+ end
8
+
9
+ @stack = []
10
+ def self.create desc, block
11
+ group = self.new desc, @stack.last
12
+ @example_groups << group
13
+
14
+ @stack << group
15
+ group.instance_eval &block
16
+ @stack.pop
17
+ end
18
+
19
+ def initialize desc, parent
20
+ @desc = desc.to_s
21
+ @parent = parent
22
+ @examples = []
23
+
24
+ @before_hooks = []
25
+ @after_hooks = []
26
+ end
27
+
28
+ def it(desc, &block)
29
+ @examples << Example.new(self, desc, block)
30
+ end
31
+
32
+ def async(desc, &block)
33
+ example = Example.new(self, desc, block)
34
+ example.asynchronous = true
35
+ @examples << example
36
+ end
37
+
38
+ def it_behaves_like(*objs)
39
+ end
40
+
41
+ def before type = :each, &block
42
+ raise "unsupported before type: #{type}" unless type == :each
43
+ @before_hooks << block
44
+ end
45
+
46
+ def after type = :each, &block
47
+ raise "unsupported after type: #{type}" unless type == :each
48
+ @after_hooks << block
49
+ end
50
+
51
+ def before_hooks
52
+ @parent ? [].concat(@parent.before_hooks).concat(@before_hooks) : @before_hooks
53
+ end
54
+
55
+ def after_hooks
56
+ @parent ? [].concat(@parent.after_hooks).concat(@after_hooks) : @after_hooks
57
+ end
58
+
59
+ def run(runner)
60
+ @runner = runner
61
+ @runner.example_group_started self
62
+
63
+ @running_examples = @examples.dup
64
+ run_next_example
65
+ end
66
+
67
+ def run_next_example
68
+ if @running_examples.empty?
69
+ @runner.example_group_finished self
70
+ else
71
+ @running_examples.shift.run
72
+ end
73
+ end
74
+
75
+ def example_started(example)
76
+ @runner.example_started(example)
77
+ end
78
+
79
+ def example_passed(example)
80
+ @runner.example_passed(example)
81
+ run_next_example
82
+ end
83
+
84
+ def example_failed(example)
85
+ @runner.example_failed(example)
86
+ run_next_example
87
+ end
88
+
89
+ def description
90
+ @parent ? "#{@parent.description} #{@desc}" : @desc
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,55 @@
1
+ module Opal
2
+ module Spec
3
+ class ExpectationNotMetError < StandardError; end
4
+
5
+ module Expectations
6
+ def should matcher = nil
7
+ if matcher
8
+ matcher.match self
9
+ else
10
+ Opal::Spec::PositiveOperatorMatcher.new self
11
+ end
12
+ end
13
+
14
+ def should_not matcher = nil
15
+ if matcher
16
+ matcher.not_match self
17
+ else
18
+ Opal::Spec::NegativeOperatorMatcher.new self
19
+ end
20
+ end
21
+
22
+ def be_kind_of expected
23
+ Opal::Spec::BeKindOfMatcher.new expected
24
+ end
25
+
26
+ def be_nil
27
+ Opal::Spec::BeNilMatcher.new nil
28
+ end
29
+
30
+ def be_true
31
+ Opal::Spec::BeTrueMatcher.new true
32
+ end
33
+
34
+ def be_false
35
+ Opal::Spec::BeFalseMatcher.new false
36
+ end
37
+
38
+ def eq(expected)
39
+ Opal::Spec::EqlMatcher.new expected
40
+ end
41
+
42
+ def equal expected
43
+ Opal::Spec::EqualMatcher.new expected
44
+ end
45
+
46
+ def raise_error expected
47
+ Opal::Spec::RaiseErrorMatcher.new expected
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ class Object
54
+ include Opal::Spec::Expectations
55
+ end
@@ -1,6 +1,6 @@
1
1
  module Kernel
2
2
  def describe desc, &block
3
- OpalSpec::ExampleGroup.create desc, block
3
+ Opal::Spec::ExampleGroup.create desc, block
4
4
  end
5
5
 
6
6
  def mock obj
@@ -0,0 +1,106 @@
1
+ module Opal
2
+ module Spec
3
+ class Matcher
4
+ def initialize actual
5
+ @actual = actual
6
+ end
7
+
8
+ def failure message
9
+ raise Opal::Spec::ExpectationNotMetError, message
10
+ end
11
+ end
12
+
13
+ class PositiveOperatorMatcher < Matcher
14
+ def == expected
15
+ if @actual == expected
16
+ true
17
+ else
18
+ failure "expected: #{expected.inspect}, got: #{@actual.inspect} (using ==)."
19
+ end
20
+ end
21
+ end
22
+
23
+ class NegativeOperatorMatcher < Matcher
24
+ def == expected
25
+ if @actual == expected
26
+ failure "expected: #{expected.inspect} not to be #{@actual.inspect} (using ==)."
27
+ end
28
+ end
29
+ end
30
+
31
+ class BeKindOfMatcher < Matcher
32
+ def match expected
33
+ unless expected.kind_of? @actual
34
+ failure "expected #{expected.inspect} to be a kind of #{@actual.name}, not #{expected.class.name}."
35
+ end
36
+ end
37
+ end
38
+
39
+ class BeNilMatcher < Matcher
40
+ def match expected
41
+ unless expected.nil?
42
+ failure "expected #{expected.inspect} to be nil."
43
+ end
44
+ end
45
+ end
46
+
47
+ class BeTrueMatcher < Matcher
48
+ def match expected
49
+ unless expected == true
50
+ failure "expected #{expected.inspect} to be true."
51
+ end
52
+ end
53
+ end
54
+
55
+ class BeFalseMatcher < Matcher
56
+ def match expected
57
+ unless expected == false
58
+ failure "expected #{expected.inspect} to be false."
59
+ end
60
+ end
61
+ end
62
+
63
+ class EqlMatcher < Matcher
64
+ def match(expected)
65
+ unless expected == @actual
66
+ failure "expected: #{expected.inspect}, got: #{@actual.inspect} (using ==)."
67
+ end
68
+ end
69
+
70
+ def not_match(expected)
71
+ if expected.equal? @actual
72
+ failure "expected: #{expected.inspect} not to be #{@actual.inspect} (using ==)."
73
+ end
74
+ end
75
+ end
76
+
77
+ class EqualMatcher < Matcher
78
+ def match expected
79
+ unless expected.equal? @actual
80
+ failure "expected #{@actual.inspect} to be the same as #{expected.inspect}."
81
+ end
82
+ end
83
+
84
+ def not_match expected
85
+ if expected.equal? @actual
86
+ failure "expected #{@actual.inspect} not to be equal to #{expected.inspect}."
87
+ end
88
+ end
89
+ end
90
+
91
+ class RaiseErrorMatcher < Matcher
92
+ def match block
93
+ should_raise = false
94
+ begin
95
+ block.call
96
+ should_raise = true
97
+ rescue => e
98
+ end
99
+
100
+ if should_raise
101
+ failure "expected #{@actual} to be raised, but nothing was."
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end