mache 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7d084d201649e516f630f6a837d312fcd6e62ec
4
- data.tar.gz: 5cc13f16c2078c326c9979d953690cf7c3f34eb6
3
+ metadata.gz: 912b56e289aa3e69473fd758575e2f43afe3818b
4
+ data.tar.gz: abcf1936d0c0c3cf08c6c27a8148f7107333500b
5
5
  SHA512:
6
- metadata.gz: afd3efc23afd21149227b50c0a461cb3f61ce49b30d4894560d76e1a6b9befdc850518b84247f4ff91ec4694eb5c5b73760c8c2da87dfc111efb190018fa05ef
7
- data.tar.gz: d05c14504c5bc9ed11b9f0e918307b53860b242a68cf7fa42dc1ff67f286269cb9f89a98d91089d6e554be099eed786e07199aedcaed058a2e3b20e2aef6999e
6
+ metadata.gz: 0d6ad5456be773a9fe544700e8377227fd4bd50be9d22baca2d94fccd641c212a3c62189114a3de9b59a757eb1fd39852cc3a67230e288b790636cea1bac9ae3
7
+ data.tar.gz: 8ad576449372f1966b1fcba9e6db5753cb6d1b41c403307496041958270451b3de0b6f621a9b2c8dfbdbbe1945e99d4416a7ed3f4449cd154a42d5453311f3da
data/.rubocop.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  AllCops:
2
2
  Exclude:
3
- - "bin/**/*"
4
3
  - "mache.gemspec"
5
4
 
6
5
  Metrics/BlockLength:
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 2.1.0
4
+
5
+ - Add flash and routing helpers.
6
+
7
+ ## 2.0.0
8
+
9
+ - Ensure the klass argument is a subclass of `Node` in the DSL methods.
10
+ - Remove `Component` class.
11
+
12
+ ## 1.0.1
13
+
14
+ - Update documentation.
15
+
16
+ ## 1.0.0
17
+
18
+ - Initial release.
data/README.md CHANGED
@@ -2,27 +2,41 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/nullobject/mache.svg?branch=master)](https://travis-ci.org/nullobject/mache)
4
4
 
5
- Mâché (pronounced "mash-ay") helps you to write cleaner and more expressive
6
- acceptance tests for your web applications using page objects.
5
+ Mâché (pronounced "mash-ay") is a tool that helps you to write cleaner and more
6
+ expressive acceptance tests for your Ruby web applications using page objects.
7
+
8
+ ## Table of contents
9
+
10
+ * [Mâché](#mâché)
11
+ * [Table of contents](#table-of-contents)
12
+ * [What is a page object?](#what-is-a-page-object)
13
+ * [Getting started](#getting-started)
14
+ * [Elements](#elements)
15
+ * [Components](#components)
16
+ * [Helpers](#helpers)
17
+ * [Example](#example)
18
+ * [API documentation](#api-documentation)
19
+ * [Contributing](#contributing)
20
+ * [License](#license)
7
21
 
8
22
  ## What is a page object?
9
23
 
10
24
  A [page object](https://martinfowler.com/bliki/PageObject.html) is a data
11
- structure which provides an interface to your web application for the purposes
25
+ structure that provides an interface to your web application for the purposes
12
26
  of test automation. For example, it could represent a single HTML page, or
13
27
  perhaps even a fragment of HTML on a page.
14
28
 
15
- From Martin Fowler:
29
+ From [Martin Fowler](https://martinfowler.com/bliki/PageObject.html):
16
30
 
17
31
  > A page object wraps an HTML page, or fragment, with an application-specific
18
32
  > API, allowing you to manipulate page elements without digging around in the
19
33
  > HTML.
20
34
 
21
- [Capybara](https://github.com/teamcapybara/capybara) can only get us part of
22
- the way there. It allows us to work with an API rather than manipulating the
23
- HTML directly, but what it provides isn't an *application specific* API. It
24
- gives us low-level API methods like `find`, `fill_in`, and `click_button`, but
25
- it doesn't provide us with high-level methods to do things like "sign in to the
35
+ [Capybara](https://github.com/teamcapybara/capybara) can get us part of the way
36
+ there. It allows us to work with an API rather than manipulating the HTML
37
+ directly, but what it provides isn't an *application specific* API. It gives us
38
+ low-level API methods like `find`, `fill_in`, and `click_button`, but it
39
+ doesn't provide us with high-level methods to do things like "sign in to the
26
40
  app" or "click the Dashboard item in the navigation bar".
27
41
 
28
42
  This is where page objects come in. Using Mâché we can for instance define a
@@ -40,11 +54,14 @@ HTML fragment for the welcome page in our app:
40
54
  <body>
41
55
  <header>
42
56
  <h1>Welcome</h1>
57
+ <div id="flash" class="notice">lorem ipsum</div>
43
58
  </header>
44
59
  <nav>
45
- <a href="#" class="selected">foo</a>
46
- <a href="#">bar</a>
47
- <a href="#">baz</a>
60
+ <ul>
61
+ <li><a href="/foo" class="selected">foo</a></li>
62
+ <li><a href="/bar">bar</a></li>
63
+ <li><a href="/baz">baz</a></li>
64
+ </ul>
48
65
  </nav>
49
66
  <main>
50
67
  lorem ipsum
@@ -58,6 +75,8 @@ the `Mache::Page` class. The only method our class needs to provide is `path`,
58
75
  this tells Mâché where to go when we want to visit the page:
59
76
 
60
77
  ```ruby
78
+ require "mache"
79
+
61
80
  class WelcomePage < Mache::Page
62
81
  def path
63
82
  "/welcome"
@@ -75,13 +94,7 @@ page.current? # true
75
94
  Mâché also handily exposes the Capybara API on our page object:
76
95
 
77
96
  ```ruby
78
- page.find("body > main").text # "lorem ipsum"
79
- ```
80
-
81
- We can also use the `node` attribute to get the underlying Capybara node object:
82
-
83
- ```ruby
84
- page.node # <Capybara::Node>
97
+ page.find("main").text # "lorem ipsum"
85
98
  ```
86
99
 
87
100
  ### Elements
@@ -93,8 +106,10 @@ that we expect to find on the page using a CSS selector.
93
106
  Let's define a `main` element to represent the main section of our HTML page:
94
107
 
95
108
  ```ruby
109
+ require "mache"
110
+
96
111
  class WelcomePage < Mache::Page
97
- element :main, "body > main"
112
+ element :main, "main"
98
113
 
99
114
  def path
100
115
  "/welcome"
@@ -105,7 +120,6 @@ end
105
120
  We can query the `main` element as an attribute of our page object:
106
121
 
107
122
  ```ruby
108
- page.has_main? # true
109
123
  page.main.text # "lorem ipsum"
110
124
  ```
111
125
 
@@ -118,6 +132,8 @@ A component can contain any number of elements (or even other components).
118
132
  Let's define a `Header` component to represent the header of our HTML page:
119
133
 
120
134
  ```ruby
135
+ require "mache"
136
+
121
137
  class Header < Mache::Node
122
138
  element :title, "h1"
123
139
  end
@@ -127,9 +143,11 @@ We can mount the `Header` component in our page object class at a given CSS
127
143
  selector using the `component` macro:
128
144
 
129
145
  ```ruby
146
+ require "mache"
147
+
130
148
  class WelcomePage < Mache::Page
131
149
  component :header, Header, "header"
132
- element :main, "body > main"
150
+ element :main, "main"
133
151
 
134
152
  def path
135
153
  "/welcome"
@@ -140,17 +158,65 @@ end
140
158
  Querying a component of our page object is much the same as with an element:
141
159
 
142
160
  ```ruby
143
- page.has_header? # true
144
161
  page.header.title.text # "Welcome"
145
162
  ```
146
163
 
164
+ ### Helpers
165
+
166
+ Mâché provides helpers for testing Rails apps.
167
+
168
+ #### Flash
169
+
170
+ The `Flash` helper provides methods for testing flash messages. First define a
171
+ flash in your page object class:
172
+
173
+ ```ruby
174
+ require "mache"
175
+ require "mache/helpers/rails"
176
+
177
+ class WelcomePage < Mache::Page
178
+ include Mache::Helpers::Rails::Flash
179
+
180
+ flash "#flash"
181
+ end
182
+ ```
183
+
184
+ Then you can query the flash on your page object:
185
+
186
+ ```ruby
187
+ page.has_notice_message?("Welcome to the app")
188
+ page.has_alert_message?("A horrible error occurred")
189
+ ```
190
+
191
+ #### Routes
192
+
193
+ The `Routes` helper mixes the Rails URL helpers into your page object class.
194
+ This allows you to use the `*_path` and `*_url` methods as you normally would
195
+ in your Rails.
196
+
197
+ ```ruby
198
+ require "mache"
199
+ require "mache/helpers/rails"
200
+
201
+ class WelcomePage < Mache::Page
202
+ include Mache::Helpers::Rails::Routes
203
+
204
+ def path
205
+ welcome_path
206
+ end
207
+ end
208
+ ```
209
+
147
210
  ## Example
148
211
 
149
- Let's look at a more complete example for our `WelcomePage`. Note that the
150
- `Header`, `NavItem`, and `Nav` components can be reused in any other page
212
+ Let's look at an example of an acceptance test for our `WelcomePage`. Note that
213
+ the `Header`, `NavItem`, and `Nav` components can be reused in any other page
151
214
  object classes we may define later for our web application.
152
215
 
153
216
  ```ruby
217
+ require "mache"
218
+ require "mache/helpers/rails"
219
+
154
220
  class Header < Mache::Node
155
221
  element :title, "h1"
156
222
  end
@@ -163,22 +229,26 @@ end
163
229
 
164
230
  class Nav < Mache::Node
165
231
  components :items, NavItem, "a"
232
+
233
+ def selected_item
234
+ items.find(&:selected?)
235
+ end
166
236
  end
167
237
 
168
238
  class WelcomePage < Mache::Page
239
+ include Mache::Helpers::Rails::Flash
240
+ include Mache::Helpers::Rails::Routes
241
+
169
242
  component :header, Header, "header"
170
243
  component :nav, Nav, "nav"
171
244
  element :main, "main"
245
+ flash "#flash"
172
246
 
173
247
  def path
174
- "/welcome"
248
+ welcome_path
175
249
  end
176
250
  end
177
- ```
178
-
179
- We can use our page objects to write very expressive acceptance tests:
180
251
 
181
- ```ruby
182
252
  feature "Welcome page" do
183
253
  let(:home_page) { WelcomePage.visit }
184
254
 
@@ -193,13 +263,34 @@ feature "Welcome page" do
193
263
  expect(home_page).to have_nav
194
264
  expect(home_page.nav).to have_items
195
265
  expect(home_page.nav.items.count).to be(3)
196
- expect(home_page.nav.items[0]).to be_selected
197
266
  expect(home_page.nav.items[0].text).to eq("foo")
198
267
  expect(home_page.nav.items[1].text).to eq("bar")
199
268
  expect(home_page.nav.items[2].text).to eq("baz")
269
+ expect(home_page.nav.selected_item).to eq("foo")
200
270
 
201
271
  # main
202
272
  expect(home_page.main.text).to eq("lorem ipsum")
273
+
274
+ # flash
275
+ expect(home_page).to have_flash
276
+ expect(home_page).to have_notice_message("lorem ipsum")
203
277
  end
204
278
  end
205
279
  ```
280
+
281
+ ## API documentation
282
+
283
+ Read the [API documentation](http://www.rubydoc.info/gems/mache) on RubyDoc.
284
+
285
+ ## Contributing
286
+
287
+ Pull requests are welcome. Please ensure that you run the tests before
288
+ submitting your PR:
289
+
290
+ ```
291
+ > bundle exec rake
292
+ ```
293
+
294
+ ## License
295
+
296
+ Mâché is licensed under the [MIT License](/LICENSE).
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require "rubocop/rake_task"
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
  RuboCop::RakeTask.new
7
7
 
8
- task default: [:spec, :rubocop]
8
+ task default: %w(spec rubocop)
data/lib/mache/dsl.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  module Mache
2
+ # The {DSL} module is mixed into the {Node} class to provide the DSL for
3
+ # defining elements and components.
4
+ #
2
5
  # See {ClassMethods} for documentation.
3
- module DSL # :nodoc:
6
+ module DSL
4
7
  def self.included(base)
5
- base.extend ClassMethods
8
+ base.extend(ClassMethods)
6
9
  end
7
10
 
8
11
  # Provides a set of macro-like methods for wrapping HTML fragments in {Node}
@@ -57,9 +60,9 @@ module Mache
57
60
 
58
61
  # Defines an element that wraps an HTML fragment.
59
62
  #
60
- # @param name [String, Symbol] the elements collection name
61
- # @param selector [String] the selector to find the element
62
- # @param options [Hash] the options to pass to the Capybara finder
63
+ # @param name [String, Symbol] a name for the element
64
+ # @param selector [String] a selector to find the element
65
+ # @param options [Hash] a hash of options to pass to the Capybara finder
63
66
  def element(name, selector, options = {})
64
67
  define_method(name.to_s) do
65
68
  Node.new(node: @node.find(selector, options))
@@ -70,9 +73,9 @@ module Mache
70
73
 
71
74
  # Defines a collection of elements that wrap HTML fragments.
72
75
  #
73
- # @param name [String, Symbol] the elements collection name
74
- # @param selector [String] the selector to find the elements
75
- # @param options [Hash] the options to pass to the Capybara finder
76
+ # @param name [String, Symbol] a name for the elements collection
77
+ # @param selector [String] a selector to find the elements
78
+ # @param options [Hash] a hash of options to pass to the Capybara finder
76
79
  def elements(name, selector, options = {})
77
80
  options = {minimum: 1}.merge(options)
78
81
 
@@ -87,10 +90,10 @@ module Mache
87
90
 
88
91
  # Defines a component that wraps an HTML fragment.
89
92
  #
90
- # @param name [String, Symbol] the elements collection name
91
- # @param klass [Class] the component class
92
- # @param selector [String] the selector to find the component
93
- # @param options [Hash] the options to pass to the Capybara finder
93
+ # @param name [String, Symbol] a name for the component
94
+ # @param klass [Class] a component class
95
+ # @param selector [String] a selector to find the component
96
+ # @param options [Hash] a hash of options to pass to the Capybara finder
94
97
  def component(name, klass, selector, options = {})
95
98
  unless klass < Node
96
99
  raise ArgumentError, "Must be given a subclass of Node"
@@ -105,10 +108,10 @@ module Mache
105
108
 
106
109
  # Defines a collection of components that wrap HTML fragments.
107
110
  #
108
- # @param name [String, Symbol] the elements collection name
109
- # @param klass [Class] the component class
110
- # @param selector [String] the selector to find the components
111
- # @param options [Hash] the options to pass to the Capybara finder
111
+ # @param name [String, Symbol] a name for the components collection
112
+ # @param klass [Class] a component class
113
+ # @param selector [String] a selector to find the components
114
+ # @param options [Hash] a hash of options to pass to the Capybara finder
112
115
  def components(name, klass, selector, options = {})
113
116
  unless klass < Node
114
117
  raise ArgumentError, "Must be given a subclass of Node"
@@ -0,0 +1,31 @@
1
+ module Mache
2
+ module Helpers
3
+ module Rails
4
+ # The {Flash} module can be Included into page object classes that support
5
+ # flash behaviour.
6
+ module Flash
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods # :nodoc:
12
+ def flash(selector)
13
+ element :flash, selector
14
+ end
15
+ end
16
+
17
+ # rubocop:disable Style/PredicateName
18
+ def has_notice_message?(text)
19
+ css_class = flash[:class] || ""
20
+ css_class.include?("notice") && flash.text =~ /\s*#{text}\s*/
21
+ end
22
+
23
+ def has_alert_message?(text)
24
+ css_class = flash[:class] || ""
25
+ css_class.include?("error") && flash.text =~ /\s*#{text}\s*/
26
+ end
27
+ # rubocop:enable Style/PredicateName
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Mache
2
+ module Helpers
3
+ module Rails
4
+ # The {Routes} module can be Included into page object classes that
5
+ # support routing.
6
+ module Routes
7
+ def self.included(base)
8
+ base.class_eval do
9
+ include ::Rails.application.routes.url_helpers
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ require "mache/helpers/rails/flash"
2
+ require "mache/helpers/rails/routes"
data/lib/mache/node.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require "mache/dsl"
2
2
 
3
3
  module Mache
4
- # The Node class represents a wrapped HTML page, or fragment. It exposes all
4
+ # The {Node} class represents a wrapped HTML page, or fragment. It exposes all
5
5
  # methods from the Mache {DSL}, and forwards any Capybara API methods to the
6
6
  # {#node} object.
7
7
  #
@@ -9,14 +9,14 @@ module Mache
9
9
  class Node
10
10
  include DSL
11
11
 
12
- # The underlying Capybara node object wrapped by this node.
12
+ # The underlying Capybara node object wrapped by this instance.
13
13
  #
14
14
  # @return [Capybara::Node] the node object
15
15
  attr_reader :node
16
16
 
17
17
  # Returns a new instance of Node.
18
18
  #
19
- # @param node [Capybara::Node] the Capybara node object to wrap
19
+ # @param node [Capybara::Node] a Capybara node object to wrap
20
20
  def initialize(node:)
21
21
  @node ||= node
22
22
  end
data/lib/mache/page.rb CHANGED
@@ -2,8 +2,8 @@ require "capybara"
2
2
  require "mache/node"
3
3
 
4
4
  module Mache
5
- # The Page class wraps an HTML page with an application-specific API. You can
6
- # extend it to define your own API for manipulating the pages of your web
5
+ # The {Page} class wraps an HTML page with an application-specific API. You
6
+ # can extend it to define your own API for manipulating the pages of your web
7
7
  # application.
8
8
  #
9
9
  # @example
@@ -29,8 +29,8 @@ module Mache
29
29
 
30
30
  # Returns a new page object.
31
31
  #
32
- # @param node [Capybara::Node] the Capybara node to attach to
33
- # @param path [String] the path where the page is located
32
+ # @param node [Capybara::Node] a Capybara node to attach to
33
+ # @param path [String] a path to where the page is located
34
34
  def initialize(node: Capybara.current_session, path: nil)
35
35
  @node ||= node
36
36
  @path ||= path
data/lib/mache/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mache
2
- VERSION = "2.0.0".freeze
2
+ VERSION = "2.1.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mache
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Bassett
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-13 00:00:00.000000000 Z
11
+ date: 2017-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -108,6 +108,7 @@ files:
108
108
  - ".ruby-version"
109
109
  - ".travis.yml"
110
110
  - ".yardopts"
111
+ - CHANGELOG.md
111
112
  - Gemfile
112
113
  - LICENSE
113
114
  - README.md
@@ -115,6 +116,9 @@ files:
115
116
  - bin/console
116
117
  - lib/mache.rb
117
118
  - lib/mache/dsl.rb
119
+ - lib/mache/helpers/rails.rb
120
+ - lib/mache/helpers/rails/flash.rb
121
+ - lib/mache/helpers/rails/routes.rb
118
122
  - lib/mache/node.rb
119
123
  - lib/mache/page.rb
120
124
  - lib/mache/version.rb
@@ -139,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
143
  version: '0'
140
144
  requirements: []
141
145
  rubyforge_project:
142
- rubygems_version: 2.5.2
146
+ rubygems_version: 2.6.11
143
147
  signing_key:
144
148
  specification_version: 4
145
149
  summary: A library for writing cleaner and more expressive acceptance tests using