mache 1.0.0 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a33c9e84520e543f6693fd7895d9fb6c6835626
4
- data.tar.gz: 7370d76223bd10fe2444e2d091e9ddd64219181a
3
+ metadata.gz: 57a3cffa66255ddaa95d41917a45842c4eb4f6e1
4
+ data.tar.gz: 54565c4f1a3b141facd22ecc3f70acf20d7402a7
5
5
  SHA512:
6
- metadata.gz: 771ff8e66362d6788b046bb94e092a100dacd2f952ee960f0b2530b47eca889a0b8a2a57ba7c4ac40116214663d3d14ca89db988e861f6ed46ed1d52c0bc37a9
7
- data.tar.gz: 4b203e4da5927d68605208ed06b8aa54a81d7c3c8f69d5c1f19cc13b1019e9c3fd7442cfb857aa11ac244b634d967a4fd1badcdc9e9ae3c28db66115b29b634b
6
+ metadata.gz: a713135868ef06a9df6cf2e40a430703de6eac61fe96e6c49eabc3fe039a71e0aa7b31fccac4ba56a02487e4c25af1411ac520b3c3728ae23a2b280723d1675f
7
+ data.tar.gz: 440ebba8dc3a9149e259c80640aa49a738f2e8de5ff967ec3b42482f0ce1a9d13ab466ebac58fc5bd324db0a61cb50770009cd6b162d9fe8ac668e200ac711ea
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  *.gem
2
2
  .bundle
3
+ .yardoc
3
4
  Gemfile.lock
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/README.md CHANGED
@@ -1,12 +1,39 @@
1
- # Mache
1
+ # Mâché
2
2
 
3
3
  [![Build Status](https://travis-ci.org/nullobject/mache.svg?branch=master)](https://travis-ci.org/nullobject/mache)
4
4
 
5
- A [page object](https://martinfowler.com/bliki/PageObject.html) library for writing cleaner acceptance tests with [Capybara](https://github.com/teamcapybara/capybara).
5
+ Mâché (pronounced "mash-ay") helps you to write cleaner and more expressive
6
+ acceptance tests for your web applications using page objects.
7
+
8
+ ## What is a page object?
9
+
10
+ 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
12
+ of test automation. For example, it could represent a single HTML page, or
13
+ perhaps even a fragment of HTML on a page.
14
+
15
+ From Martin Fowler:
16
+
17
+ > A page object wraps an HTML page, or fragment, with an application-specific
18
+ > API, allowing you to manipulate page elements without digging around in the
19
+ > HTML.
20
+
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
26
+ app" or "click the Dashboard item in the navigation bar".
27
+
28
+ This is where page objects come in. Using Mâché we can for instance define a
29
+ page object class called `SignInPage` and use it any time we want to automate
30
+ authenticating with our app. It could handle visiting the sign in page,
31
+ entering the user's credentials, and clicking the "Sign in" button.
6
32
 
7
33
  ## Getting started
8
34
 
9
- Consider the following HTML snippet:
35
+ Let's dive straight in and take a look at an example. Consider the following
36
+ HTML fragment for the welcome page in our app:
10
37
 
11
38
  ```html
12
39
  <html>
@@ -26,9 +53,9 @@ Consider the following HTML snippet:
26
53
  </html>
27
54
  ```
28
55
 
29
- To define a page object class to wrap this HTML snippet, we extend the
30
- `Mache::Page` class. The only method our class needs to provide is `path`, this
31
- tells Mache where to go when we want to visit the page.
56
+ To define a `WelcomePage` page object class to wrap this HTML page, we extend
57
+ the `Mache::Page` class. The only method our class needs to provide is `path`,
58
+ this tells Mâché where to go when we want to visit the page:
32
59
 
33
60
  ```ruby
34
61
  class WelcomePage < Mache::Page
@@ -38,26 +65,32 @@ class WelcomePage < Mache::Page
38
65
  end
39
66
  ```
40
67
 
41
- This is how we visit our page object:
68
+ We can visit our welcome page using our page object:
42
69
 
43
70
  ```ruby
44
71
  page = WelcomePage.visit
45
72
  page.current? # true
46
73
  ```
47
74
 
48
- Any Capybara API methods will be forwarded to the underlying node:
75
+ Mâché also handily exposes the Capybara API on our page object:
49
76
 
50
77
  ```ruby
51
78
  page.find("body > main").text # "lorem ipsum"
52
79
  ```
53
80
 
81
+ We can also use the `node` attribute to get the underlying Capybara node object:
82
+
83
+ ```ruby
84
+ page.node # <Capybara::Node>
85
+ ```
86
+
54
87
  ### Elements
55
88
 
56
89
  To make our page object more useful, we can define an element on our page
57
90
  object class using the `element` macro. An element is simply a HTML element
58
91
  that we expect to find on the page using a CSS selector.
59
92
 
60
- Let's define a `main` element:
93
+ Let's define a `main` element to represent the main section of our HTML page:
61
94
 
62
95
  ```ruby
63
96
  class WelcomePage < Mache::Page
@@ -69,7 +102,7 @@ class WelcomePage < Mache::Page
69
102
  end
70
103
  ```
71
104
 
72
- Now we can query the element on our page object:
105
+ We can query the `main` element as an attribute of our page object:
73
106
 
74
107
  ```ruby
75
108
  page.has_main? # true
@@ -79,10 +112,12 @@ page.main.text # "lorem ipsum"
79
112
 
80
113
  ### Components
81
114
 
82
- For elements that can be shared across an number of page object classes, it may
115
+ For elements that can be shared across any number of page object classes it may
83
116
  be useful to define a reusable component by extending the `Mache::Component`
84
- class. A component class can contain any number of elements or other
85
- components:
117
+ class. A component can contain any number of elements (or even other
118
+ components).
119
+
120
+ Let's define a `Header` component to represent the header of our HTML page:
86
121
 
87
122
  ```ruby
88
123
  class Header < Mache::Component
@@ -90,8 +125,8 @@ class Header < Mache::Component
90
125
  end
91
126
  ```
92
127
 
93
- Our page object class can mount our component at a given CSS selector using the
94
- `component` macro:
128
+ We can mount the `Header` component in our page object class at a given CSS
129
+ selector using the `component` macro:
95
130
 
96
131
  ```ruby
97
132
  class WelcomePage < Mache::Page
@@ -104,7 +139,7 @@ class WelcomePage < Mache::Page
104
139
  end
105
140
  ```
106
141
 
107
- Querying a component on our page object is much the same as an element:
142
+ Querying a component of our page object is much the same as with an element:
108
143
 
109
144
  ```ruby
110
145
  page.has_header? # true
@@ -114,7 +149,9 @@ page.header.title.text # "Welcome"
114
149
 
115
150
  ## Example
116
151
 
117
- Let's look at a more complete example for our `WelcomePage`:
152
+ Let's look at a more complete example for our `WelcomePage`. Note that the
153
+ `Header`, `NavItem`, and `Nav` components can be reused in any other page
154
+ object classes we may define later for our web application.
118
155
 
119
156
  ```ruby
120
157
  class Header < Mache::Component
@@ -142,28 +179,29 @@ class WelcomePage < Mache::Page
142
179
  end
143
180
  ```
144
181
 
145
- We can use our page objects to write expressive tests:
182
+ We can use our page objects to write very expressive acceptance tests:
146
183
 
147
184
  ```ruby
148
- feature "Home page" do
185
+ feature "Welcome page" do
149
186
  let(:home_page) { WelcomePage.visit }
150
187
 
151
- scenario "A user visits the home page" do
188
+ scenario "A user visits the welcome page" do
152
189
  expect(home_page).to be_current
153
190
 
191
+ # header
154
192
  expect(home_page).to have_header
155
193
  expect(home_page.header.title).to eq("Welcome")
156
194
 
195
+ # nav
157
196
  expect(home_page).to have_nav
158
197
  expect(home_page.nav).to have_items
159
198
  expect(home_page.nav.items.count).to be(3)
160
-
199
+ expect(home_page.nav.items[0]).to be_selected
161
200
  expect(home_page.nav.items[0].text).to eq("foo")
162
201
  expect(home_page.nav.items[1].text).to eq("bar")
163
202
  expect(home_page.nav.items[2].text).to eq("baz")
164
203
 
165
- expect(home_page.nav.items[0]).to be_selected
166
-
204
+ # main
167
205
  expect(home_page.main.text).to eq("lorem ipsum")
168
206
  end
169
207
  end
@@ -1,6 +1,22 @@
1
1
  require "mache/node"
2
2
 
3
3
  module Mache
4
+ # The Component class wraps a fragment of HTML and can be used in any number
5
+ # of {Page} classes using the `component` macro. A component can contain
6
+ # elements and other components.
7
+ #
8
+ # @example
9
+ #
10
+ # class NavItem < Mache::Component
11
+ # def selected?
12
+ # node[:class].include?("selected")
13
+ # end
14
+ # end
15
+ #
16
+ # class Nav < Mache::Component
17
+ # components :items, NavItem, "a"
18
+ # end
19
+ #
4
20
  class Component < Node
5
21
  end
6
22
  end
data/lib/mache/dsl.rb CHANGED
@@ -24,16 +24,18 @@ module Mache
24
24
  define_helper_methods(name, selector)
25
25
  end
26
26
 
27
- def component(name, type, selector)
27
+ def component(name, klass, selector)
28
28
  define_method(name.to_s) do
29
- type.new(@node.find(selector))
29
+ klass.new(node: @node.find(selector))
30
30
  end
31
31
  define_helper_methods(name, selector)
32
32
  end
33
33
 
34
- def components(name, type, selector)
34
+ def components(name, klass, selector)
35
35
  define_method(name.to_s) do
36
- @node.all(selector, minimum: 1).map { |element| type.new(element) }
36
+ @node.all(selector, minimum: 1).map do |element|
37
+ klass.new(node: element)
38
+ end
37
39
  end
38
40
  define_helper_methods(name, selector)
39
41
  end
data/lib/mache/node.rb CHANGED
@@ -1,17 +1,27 @@
1
1
  require "mache/dsl"
2
2
 
3
3
  module Mache
4
- # An abstract class that wraps a capybara node object and exposes the mache
5
- # DSL. It also delegates any capybara methods to the underlying node object.
4
+ # The Node class represents a wrapped HTML page, or fragment. It exposes all
5
+ # methods from the Mache {DSL}, and forwards any Capybara API methods to the
6
+ # {#node} object.
7
+ #
8
+ # @abstract
6
9
  class Node
7
10
  include DSL
8
11
 
12
+ # The underlying Capybara node object wrapped by this node.
13
+ #
14
+ # @return [Capybara::Node] the node object
9
15
  attr_reader :node
10
16
 
11
- def initialize(node)
12
- @node = node
17
+ # Returns a new instance of Node.
18
+ #
19
+ # @param node [Capybara::Node] the Capybara node object to wrap
20
+ def initialize(node:)
21
+ @node ||= node
13
22
  end
14
23
 
24
+ # Forwards any Capybara API calls to the node object.
15
25
  def method_missing(name, *args, &block)
16
26
  if @node.respond_to?(name)
17
27
  @node.send(name, *args, &block)
@@ -20,6 +30,7 @@ module Mache
20
30
  end
21
31
  end
22
32
 
33
+ # @!visibility private
23
34
  def respond_to_missing?(name, include_private = false)
24
35
  @node.respond_to?(name) || super
25
36
  end
data/lib/mache/page.rb CHANGED
@@ -2,27 +2,60 @@ require "capybara"
2
2
  require "mache/node"
3
3
 
4
4
  module Mache
5
- # A page provides a DSL for querying a wrapped capybara node object node. A
6
- # page can also has a path which can be visited.
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
7
+ # application.
8
+ #
9
+ # @example
10
+ #
11
+ # class WelcomePage < Mache::page
12
+ # element :main, "#main"
13
+ # component :nav, Nav, "#nav"
14
+ # end
15
+ #
16
+ # page = WelcomePage.new(path: "/welcome")
17
+ # page.visit
18
+ # page.current # true
19
+ # page.main.text # lorem ipsum
20
+ #
7
21
  class Page < Node
22
+ # The path where the page is located, without any domain information.
23
+ #
24
+ # @return [String] the path string
25
+ # @example
26
+ # "/welcome"
27
+ # "/users/sign_in"
8
28
  attr_reader :path
9
29
 
30
+ # Returns a new page object.
31
+ #
32
+ # @param node [Capybara::Node] the Capybara node to attach to
33
+ # @param path [String] the path where the page is located
10
34
  def initialize(node: Capybara.current_session, path: nil)
11
35
  @node ||= node
12
36
  @path ||= path
13
37
  end
14
38
 
39
+ # Visits the page at its {#path}.
40
+ #
41
+ # @return [Page] the page object
15
42
  def visit
16
43
  @node.visit(path)
17
44
  self
18
45
  end
19
46
 
47
+ # Tests whether the page is current.
48
+ #
49
+ # @return [Boolean] `true` if the page is current, `false` otherwise
20
50
  def current?
21
51
  @node.current_path == path
22
52
  end
23
53
 
54
+ # Creates a new page object and calls {#visit} on it.
55
+ #
56
+ # @return [Page] the page object
24
57
  def self.visit
25
- new.tap(&:visit)
58
+ new.visit
26
59
  end
27
60
  end
28
61
  end
data/lib/mache/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mache
2
- VERSION = "1.0.0".freeze
2
+ VERSION = "1.0.1".freeze
3
3
  end
data/mache.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Mache::VERSION
9
9
  spec.authors = ["Joshua Bassett"]
10
10
  spec.email = ["josh.bassett@gmail.com"]
11
- spec.summary = "A page object library for writing cleaner acceptance tests with Capybara."
12
- spec.description = "Mache provides a DSL for writing page object clases to use in your acceptance tests."
11
+ spec.summary = "A library for writing cleaner and more expressive acceptance tests using page objects."
12
+ spec.description = "Mâché provides helps you to write cleaner and more expressive acceptance tests for your web applications using page objects."
13
13
  spec.homepage = "https://github.com/nullobject/mache"
14
14
  spec.license = "MIT"
15
15
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
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: 1.0.0
4
+ version: 1.0.1
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-07 00:00:00.000000000 Z
11
+ date: 2017-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -94,8 +94,8 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.47'
97
- description: Mache provides a DSL for writing page object clases to use in your acceptance
98
- tests.
97
+ description: Mâché provides helps you to write cleaner and more expressive acceptance
98
+ tests for your web applications using page objects.
99
99
  email:
100
100
  - josh.bassett@gmail.com
101
101
  executables: []
@@ -107,6 +107,7 @@ files:
107
107
  - ".rubocop.yml"
108
108
  - ".ruby-version"
109
109
  - ".travis.yml"
110
+ - ".yardopts"
110
111
  - Gemfile
111
112
  - LICENSE
112
113
  - README.md
@@ -142,5 +143,6 @@ rubyforge_project:
142
143
  rubygems_version: 2.5.2
143
144
  signing_key:
144
145
  specification_version: 4
145
- summary: A page object library for writing cleaner acceptance tests with Capybara.
146
+ summary: A library for writing cleaner and more expressive acceptance tests using
147
+ page objects.
146
148
  test_files: []