craft 0.1.0 → 0.2.0
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 +7 -0
- data/Gemfile +0 -11
- data/LICENSE.txt +2 -2
- data/README.md +4 -40
- data/Rakefile +1 -3
- data/craft.gemspec +9 -9
- data/lib/craft.rb +9 -119
- data/lib/craft/version.rb +2 -2
- data/test/craft_test.rb +50 -0
- metadata +59 -28
- data/CHANGES.md +0 -8
- data/spec/craft_spec.rb +0 -131
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0d73f28ad0726d5645b313a8692748e7513f0b8e
|
4
|
+
data.tar.gz: 63a7eda56f737e4e52e281390117f5dda143a0b4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 336a3002c19325e9079b56be7dd69d60cd02e332c1004592fe7aafd7d2b2b74385cf5bef45d3c221f6e12fde68b0798ff04f1fc5962057e47af2f408a5c6905a
|
7
|
+
data.tar.gz: 7070a01973af959e33fe286d9b68e4f36c89d930c65774916e4eca99136520f50311b0e4b618ccf96dc2e299d3f674c4ce6056f7bc70dd7f54b1c4678575131d
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2013 Hakan Ensari
|
2
2
|
|
3
3
|
MIT License
|
4
4
|
|
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
21
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,42 +1,6 @@
|
|
1
|
-
# Craft
|
1
|
+
# Craft
|
2
2
|
|
3
|
-
|
3
|
+
Build [page objects][po] in [Capybara][ca].
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
require 'craft'
|
8
|
-
require 'open-uri'
|
9
|
-
|
10
|
-
class Page < Craft
|
11
|
-
# Use CSS selectors
|
12
|
-
one :title, 'title'
|
13
|
-
|
14
|
-
# Use XPath
|
15
|
-
many :links, 'a/@href'
|
16
|
-
|
17
|
-
# Perform transforms on returned nodes
|
18
|
-
many :images, 'img', lambda { |img| img.attr('src').upcase }
|
19
|
-
|
20
|
-
# Stub attributes that don't need to be parsed
|
21
|
-
stub :spidered_at, lambda { Time.now }
|
22
|
-
end
|
23
|
-
|
24
|
-
page = Page.parse open('http://www.google.com')
|
25
|
-
|
26
|
-
page.title #=> 'Google'
|
27
|
-
page.links #=> ['http://www.google.com/imghp?hl=en&tab=wi', ...]
|
28
|
-
page.images #=> ['/LOGOS/2012/MOBY_DICK12-HP.JPG']
|
29
|
-
|
30
|
-
page.attributes #=> { :title => 'Google', :links => ... }
|
31
|
-
|
32
|
-
class Script < Craft
|
33
|
-
one :body, 'text()'
|
34
|
-
end
|
35
|
-
|
36
|
-
class Page < Craft
|
37
|
-
many :scripts, 'script', Script
|
38
|
-
end
|
39
|
-
|
40
|
-
page = Page.parse open('http://www.google.com')
|
41
|
-
page.scripts[0].body #=> 'window.google=...'
|
42
|
-
```
|
5
|
+
[po]: https://code.google.com/p/selenium/wiki/PageObjects
|
6
|
+
[ca]: https://github.com/jnicklas/capybara
|
data/Rakefile
CHANGED
data/craft.gemspec
CHANGED
@@ -6,17 +6,17 @@ require 'craft/version'
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = 'craft'
|
8
8
|
gem.version = Craft::VERSION
|
9
|
-
gem.authors = ['
|
10
|
-
gem.email = ['
|
11
|
-
gem.
|
12
|
-
gem.
|
13
|
-
out of HTML and XML.}
|
14
|
-
gem.homepage = 'http://papercavalier.com/craft/'
|
9
|
+
gem.authors = ['Hakan Ensari']
|
10
|
+
gem.email = ['hakan.ensari@papercavalier.com']
|
11
|
+
gem.summary = 'Build page objects in Capybara'
|
12
|
+
gem.homepage = 'https://github.com/hakanensari/craft'
|
15
13
|
|
16
14
|
gem.files = `git ls-files`.split($/)
|
17
|
-
gem.
|
18
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.test_files = gem.files.grep(%r{^test/})
|
19
16
|
gem.require_paths = ['lib']
|
20
17
|
|
21
|
-
gem.add_dependency '
|
18
|
+
gem.add_dependency 'capybara', '~> 2.1'
|
19
|
+
gem.add_development_dependency 'minitest'
|
20
|
+
gem.add_development_dependency 'poltergeist'
|
21
|
+
gem.add_development_dependency 'rake'
|
22
22
|
end
|
data/lib/craft.rb
CHANGED
@@ -1,127 +1,17 @@
|
|
1
|
-
require '
|
2
|
-
require 'nokogiri'
|
1
|
+
require 'capybara'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
# Examples
|
7
|
-
#
|
8
|
-
# module Transformations
|
9
|
-
# IntegerTransform = lambda { |n| Integer n.text }
|
10
|
-
# Timestamp = lambda { Time.now }
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# class Person < Craft
|
14
|
-
# include Transformations
|
15
|
-
#
|
16
|
-
# one :name, 'div.name'
|
17
|
-
# one :age, 'div.age', IntegerTransform
|
18
|
-
# many :friends, 'li.friend', Person
|
19
|
-
# stub :created_at, Timestamp
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
class Craft
|
23
|
-
class << self
|
24
|
-
# Returns an Array of names for the attributes defined in the class.
|
25
|
-
attr :attribute_names
|
26
|
-
|
27
|
-
# Define an attribute that extracts a collection of values from a parsed
|
28
|
-
# document.
|
29
|
-
#
|
30
|
-
# name - The Symbol name of the attribute.
|
31
|
-
# paths - One or more String XPath of CSS queries. An optional Proc
|
32
|
-
# transformation on the extracted value may be appended. If none is
|
33
|
-
# appended, the default transformation returns the stripped String
|
34
|
-
# value of the node.
|
35
|
-
#
|
36
|
-
# Returns nothing.
|
37
|
-
def many(name, *paths)
|
38
|
-
transform = pop_transform_from_paths paths
|
39
|
-
@attribute_names << name
|
40
|
-
|
41
|
-
define_method name do
|
42
|
-
@node.search(*paths).map { |node| instance_exec node, &transform }
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Define an attribute that extracts a single value from a parsed document.
|
47
|
-
#
|
48
|
-
# name - The Symbol name of the attribute.
|
49
|
-
# paths - One or more String XPath of CSS queries. An optional Proc
|
50
|
-
# transformation on the extracted value may be appended. If none is
|
51
|
-
# appended, the default transformation returns the stripped String
|
52
|
-
# value of the node.
|
53
|
-
#
|
54
|
-
# Returns nothing.
|
55
|
-
def one(name, *paths)
|
56
|
-
transform = pop_transform_from_paths paths
|
57
|
-
@attribute_names << name
|
58
|
-
|
59
|
-
define_method name do
|
60
|
-
instance_exec @node.at(*paths), &transform
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Parse a document.
|
65
|
-
#
|
66
|
-
# body - A String HTML or XML document.
|
67
|
-
#
|
68
|
-
# Returns an instance of its self.
|
69
|
-
def parse(body)
|
70
|
-
new Nokogiri body
|
71
|
-
end
|
72
|
-
|
73
|
-
# Define an attribute that returns a value without parsing the document.
|
74
|
-
#
|
75
|
-
# name - The Symbol name of the attribute.
|
76
|
-
# value - Some value the attribute should return. If given a Proc, the
|
77
|
-
# value will be generated dynamically (default: nil).
|
78
|
-
#
|
79
|
-
# Returns nothing.
|
80
|
-
def stub(name, value = nil)
|
81
|
-
@attribute_names << name
|
82
|
-
|
83
|
-
define_method name do
|
84
|
-
value.respond_to?(:call) ? instance_exec(&value) : value
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def to_proc
|
89
|
-
klass = self
|
90
|
-
->(node) { klass.new node, self }
|
91
|
-
end
|
3
|
+
module Craft
|
4
|
+
include Capybara::DSL
|
92
5
|
|
93
|
-
|
94
|
-
|
95
|
-
def inherited(subclass)
|
96
|
-
subclass.instance_variable_set :@attribute_names, []
|
97
|
-
end
|
6
|
+
class << self
|
7
|
+
attr_accessor :driver
|
98
8
|
|
99
|
-
def
|
100
|
-
|
101
|
-
array.pop
|
102
|
-
else
|
103
|
-
->(node) { node.text.strip if node }
|
104
|
-
end
|
9
|
+
def session
|
10
|
+
Thread.current[:session] ||= Capybara::Session.new(driver)
|
105
11
|
end
|
106
12
|
end
|
107
13
|
|
108
|
-
|
109
|
-
|
110
|
-
# Craft a new object.
|
111
|
-
#
|
112
|
-
# node - A Nokogiri::XML::Node.
|
113
|
-
def initialize(node, parent = nil)
|
114
|
-
@node = node
|
115
|
-
@parent = parent
|
116
|
-
end
|
117
|
-
|
118
|
-
# Returns the Hash attributes.
|
119
|
-
def attributes
|
120
|
-
Hash[attribute_names.map { |key| [key, self.send(key)] }]
|
121
|
-
end
|
122
|
-
|
123
|
-
# Returns an Array of names for the attributes on this object.
|
124
|
-
def attribute_names
|
125
|
-
self.class.attribute_names
|
14
|
+
def page
|
15
|
+
Craft.session
|
126
16
|
end
|
127
17
|
end
|
data/lib/craft/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '0.
|
1
|
+
module Craft
|
2
|
+
VERSION = '0.2.0'
|
3
3
|
end
|
data/test/craft_test.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'capybara/poltergeist'
|
3
|
+
require 'craft'
|
4
|
+
|
5
|
+
Craft.driver = :poltergeist
|
6
|
+
|
7
|
+
class Home
|
8
|
+
include Craft
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
visit('http://www.duckduckgo.com')
|
12
|
+
end
|
13
|
+
|
14
|
+
def search(query)
|
15
|
+
fill_in('q', with: query)
|
16
|
+
click_on('search_button_homepage')
|
17
|
+
|
18
|
+
Search.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Search
|
23
|
+
include Craft
|
24
|
+
|
25
|
+
def results
|
26
|
+
all('.results_links_deep')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class TestCraft < Minitest::Test
|
31
|
+
def test_browser
|
32
|
+
duck = Home.new
|
33
|
+
search = duck.search('ruby')
|
34
|
+
|
35
|
+
refute_empty search.results
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_session
|
39
|
+
thrs = %w(ruby python)
|
40
|
+
.map { |query|
|
41
|
+
Thread.new {
|
42
|
+
Thread.current[:current_url] = Home.new.search(query).current_url
|
43
|
+
}
|
44
|
+
}
|
45
|
+
.map(&:join)
|
46
|
+
|
47
|
+
refute_equal thrs.first[:current_url], thrs.last[:current_url]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
metadata
CHANGED
@@ -1,43 +1,80 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: craft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
- Ezekiel Templin
|
9
7
|
- Hakan Ensari
|
10
8
|
autorequire:
|
11
9
|
bindir: bin
|
12
10
|
cert_chain: []
|
13
|
-
date:
|
11
|
+
date: 2013-07-21 00:00:00.000000000 Z
|
14
12
|
dependencies:
|
15
13
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
14
|
+
name: capybara
|
17
15
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
16
|
requirements:
|
20
17
|
- - ~>
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '1
|
19
|
+
version: '2.1'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
23
|
requirements:
|
28
24
|
- - ~>
|
29
25
|
- !ruby/object:Gem::Version
|
30
|
-
version: '1
|
31
|
-
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: poltergeist
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
32
70
|
email:
|
33
|
-
-
|
71
|
+
- hakan.ensari@papercavalier.com
|
34
72
|
executables: []
|
35
73
|
extensions: []
|
36
74
|
extra_rdoc_files: []
|
37
75
|
files:
|
38
76
|
- .gitignore
|
39
77
|
- .travis.yml
|
40
|
-
- CHANGES.md
|
41
78
|
- Gemfile
|
42
79
|
- LICENSE.txt
|
43
80
|
- README.md
|
@@ -45,36 +82,30 @@ files:
|
|
45
82
|
- craft.gemspec
|
46
83
|
- lib/craft.rb
|
47
84
|
- lib/craft/version.rb
|
48
|
-
-
|
49
|
-
homepage:
|
85
|
+
- test/craft_test.rb
|
86
|
+
homepage: https://github.com/hakanensari/craft
|
50
87
|
licenses: []
|
88
|
+
metadata: {}
|
51
89
|
post_install_message:
|
52
90
|
rdoc_options: []
|
53
91
|
require_paths:
|
54
92
|
- lib
|
55
93
|
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
94
|
requirements:
|
58
|
-
- -
|
95
|
+
- - '>='
|
59
96
|
- !ruby/object:Gem::Version
|
60
97
|
version: '0'
|
61
|
-
segments:
|
62
|
-
- 0
|
63
|
-
hash: 2821744877133646519
|
64
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
99
|
requirements:
|
67
|
-
- -
|
100
|
+
- - '>='
|
68
101
|
- !ruby/object:Gem::Version
|
69
102
|
version: '0'
|
70
|
-
segments:
|
71
|
-
- 0
|
72
|
-
hash: 2821744877133646519
|
73
103
|
requirements: []
|
74
104
|
rubyforge_project:
|
75
|
-
rubygems_version:
|
105
|
+
rubygems_version: 2.0.5
|
76
106
|
signing_key:
|
77
|
-
specification_version:
|
78
|
-
summary:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Build page objects in Capybara
|
79
109
|
test_files:
|
80
|
-
-
|
110
|
+
- test/craft_test.rb
|
111
|
+
has_rdoc:
|
data/CHANGES.md
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
v0.1.0
|
2
|
-
-----------
|
3
|
-
|
4
|
-
- When nesting, make parent accessible to child.
|
5
|
-
- Craft#attributes returns the attributes.
|
6
|
-
- Craft.stub stubs a static or dynamic value that doesn't need to be parsed in
|
7
|
-
the document.
|
8
|
-
- Transforms are executed in the context of the crafted object.
|
data/spec/craft_spec.rb
DELETED
@@ -1,131 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'craft'
|
4
|
-
|
5
|
-
describe Craft do
|
6
|
-
let(:html) { '<html><ul><li>1</li><li>2</li>' }
|
7
|
-
let(:klass) { Class.new Craft }
|
8
|
-
let(:instance) { klass.parse html }
|
9
|
-
|
10
|
-
describe '.attribute_names' do
|
11
|
-
it 'is empty by default' do
|
12
|
-
klass.attribute_names.must_equal []
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'does not reference other attribute names' do
|
16
|
-
klass.stub :foo
|
17
|
-
other = Class.new(Craft) { stub :bar }
|
18
|
-
klass.attribute_names.wont_equal other.attribute_names
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
describe '.many' do
|
23
|
-
it 'extracts nodes' do
|
24
|
-
klass.many :foo, 'li'
|
25
|
-
instance.foo.must_equal %w(1 2)
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'transforms' do
|
29
|
-
klass.many :foo, 'li', ->(node) { node.text.to_i }
|
30
|
-
instance.foo.must_equal [1, 2]
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'transforms in scope' do
|
34
|
-
klass.many :foo, 'li', ->(node) { bar }
|
35
|
-
klass.send(:define_method, :bar) { 'bar' }
|
36
|
-
instance.foo.must_equal ['bar', 'bar']
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'stores attribute name' do
|
40
|
-
klass.many :foo, 'li'
|
41
|
-
klass.attribute_names.must_include :foo
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'nests' do
|
45
|
-
klass.many :foo, 'ul', Class.new(Craft)
|
46
|
-
instance.foo.each { |attr| attr.must_be_kind_of Craft }
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'has a parent when nested' do
|
50
|
-
klass.many :foo, 'li', Class.new(Craft)
|
51
|
-
instance.foo.each { |attr| attr.parent.must_equal instance }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '.one' do
|
56
|
-
it 'extracts a node' do
|
57
|
-
klass.one :foo, 'li'
|
58
|
-
instance.foo.must_equal '1'
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'transforms' do
|
62
|
-
klass.one :foo, 'li', ->(node) { node.text.to_i }
|
63
|
-
instance.foo.must_equal 1
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'transforms in scope' do
|
67
|
-
klass.one :foo, 'li', ->(node) { bar }
|
68
|
-
klass.send(:define_method, :bar) { 'bar' }
|
69
|
-
instance.foo.must_equal 'bar'
|
70
|
-
end
|
71
|
-
|
72
|
-
it 'stores attribute name' do
|
73
|
-
klass.one :foo, 'li'
|
74
|
-
klass.attribute_names.must_include :foo
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'nests' do
|
78
|
-
klass.one :foo, 'ul', Class.new(Craft)
|
79
|
-
instance.foo.must_be_kind_of Craft
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'has a parent when nested' do
|
83
|
-
klass.one :foo, 'li', Class.new(Craft)
|
84
|
-
instance.foo.parent.must_equal instance
|
85
|
-
end
|
86
|
-
|
87
|
-
describe 'given no matches' do
|
88
|
-
before { klass.one :foo, 'bar' }
|
89
|
-
|
90
|
-
it 'returns nil' do
|
91
|
-
instance.foo.must_be_nil
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
describe '.stub' do
|
97
|
-
it 'returns nil by default' do
|
98
|
-
klass.stub :foo
|
99
|
-
instance.foo.must_be_nil
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'returns a static value' do
|
103
|
-
klass.stub :foo, 1
|
104
|
-
instance.foo.must_equal 1
|
105
|
-
end
|
106
|
-
|
107
|
-
it 'returns a dynamic value' do
|
108
|
-
klass.stub :foo, -> { Time.now }
|
109
|
-
instance.foo.must_be_instance_of Time
|
110
|
-
end
|
111
|
-
|
112
|
-
it 'transforms in scope' do
|
113
|
-
klass.stub :foo, -> { bar }
|
114
|
-
klass.send(:define_method, :bar) { 'bar' }
|
115
|
-
instance.foo.must_equal 'bar'
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'stores attribute name' do
|
119
|
-
klass.stub :foo
|
120
|
-
klass.attribute_names.must_include :foo
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
describe '#attributes' do
|
125
|
-
it 'returns attributes' do
|
126
|
-
klass.stub :foo
|
127
|
-
klass.one :bar, 'li'
|
128
|
-
instance.attributes.must_equal({ foo: nil, bar: '1' })
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|