cucumber-salad 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,7 +5,7 @@ module Cucumber
5
5
  case val
6
6
  when 'yes', 'true', true
7
7
  true
8
- when 'no', 'false', false, nil
8
+ when 'no', 'false', false, nil, ''
9
9
  false
10
10
  else
11
11
  raise ArgumentError, "can't convert #{val.inspect} to boolean"
@@ -0,0 +1,21 @@
1
+ module Cucumber
2
+ module Salad
3
+ module InstanceConversions
4
+ def self.included(base)
5
+ base.send :include, Cucumber::Salad::Conversions
6
+ end
7
+
8
+ def to_boolean
9
+ Boolean(self)
10
+ end
11
+
12
+ def to_a
13
+ List(self)
14
+ end
15
+
16
+ def to_time
17
+ Timeish(self)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Cucumber
2
+ module Salad
3
+ class NodeText < String
4
+ include InstanceConversions
5
+
6
+ def initialize(node)
7
+ super node.text.strip
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Cucumber
2
+ module Salad
3
+ class Table
4
+ class CellText < String
5
+ include InstanceConversions
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ module Cucumber
2
+ module Salad
3
+ class Table
4
+ class Mapping
5
+ def initialize(settings = {})
6
+ self.key = settings[:key]
7
+ self.value_transformer = transformer(settings[:value_transformer], :pass)
8
+ self.key_transformer = transformer(settings[:key_transformer], :keyword)
9
+ end
10
+
11
+ def set(instance, row, key, value)
12
+ row[transform_key(instance, key)] = transform_value(instance, value)
13
+ end
14
+
15
+ private
16
+
17
+ attr_accessor :key, :value_transformer, :key_transformer
18
+
19
+ def transform_key(_, k)
20
+ key || key_transformer.(k)
21
+ end
22
+
23
+ def transform_value(instance, value)
24
+ instance.instance_exec(value, &value_transformer)
25
+ end
26
+
27
+ def transformer(set, fallback)
28
+ case set
29
+ when Symbol
30
+ Transformations.send(set)
31
+ when Proc
32
+ set
33
+ when nil
34
+ Transformations.send(fallback)
35
+ else
36
+ raise ArgumentError, "can't convert #{set.inspect} to transformer"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ module Cucumber
2
+ module Salad
3
+ class Table
4
+ module Transformations
5
+ def self.keyword
6
+ ->(val) { val.squeeze(' ').strip.gsub(' ', '_').sub(/\?$/, '').to_sym }
7
+ end
8
+
9
+ def self.pass
10
+ ->(val) { val }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module Cucumber
2
+ module Salad
3
+ class Table
4
+ class VoidMapping
5
+ def set(*)
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -7,39 +7,70 @@ module Cucumber
7
7
  include Conversions
8
8
 
9
9
  class << self
10
- def header_mappings
11
- @header_mappings ||=
12
- with_parent_mappings(:header) { |h, k|
13
- h[k] = k.squeeze(' ').strip.gsub(' ', '_').to_sym
14
- }
10
+ def Array(table)
11
+ new(table).to_a
12
+ end
13
+
14
+ def Hash(table)
15
+ new(table).to_h
15
16
  end
16
17
 
17
18
  def map(name, options = {}, &block)
18
- header_mappings[name.to_s] = options[:to].to_sym if options[:to]
19
- value_mappings[name.to_s] = block if block_given?
19
+ case name
20
+ when :*
21
+ set_default_mapping options, &block
22
+ else
23
+ set_mapping name, options, &block
24
+ end
20
25
  end
21
26
 
22
- def value_mappings
23
- @value_mappings ||=
24
- with_parent_mappings(:value) { |h, k|
25
- h[k] = ->(value) { value }
26
- }
27
+ def mappings
28
+ @mappings ||= Hash.
29
+ new { |h, k| h[k] = Mapping.new }.
30
+ merge(with_parent_mappings)
31
+ end
32
+
33
+ def skip(name)
34
+ case name
35
+ when :*
36
+ set_default_mapping VoidMapping
37
+ else
38
+ raise ArgumentError, "can't convert #{name.inspect} to name"
39
+ end
27
40
  end
28
41
 
29
42
  private
30
43
 
31
- def with_parent_mappings(name, &init)
32
- m = "#{name}_mappings"
44
+ def set_default_mapping(options, &block)
45
+ case options
46
+ when Hash
47
+ @mappings = Hash.
48
+ new { |h, k|
49
+ h[k] = Mapping.new(key_transformer: options[:to],
50
+ value_transformer: block) }.
51
+ merge(mappings)
52
+ when Class
53
+ @mappings = Hash.new { |h, k| h[k] = options.new }.merge(mappings)
54
+ else
55
+ raise ArgumentError, "can't convert #{options.inspect} to mapping"
56
+ end
57
+ end
58
+
59
+ def set_mapping(name, options, &block)
60
+ mappings[name] = Mapping.
61
+ new(key: options[:to], value_transformer: block)
62
+ end
33
63
 
34
- if superclass.respond_to?(m)
35
- superclass.send(m).dup
64
+ def with_parent_mappings
65
+ if superclass.respond_to?(:mappings)
66
+ superclass.send(:mappings).dup
36
67
  else
37
- Hash.new(&init)
68
+ {}
38
69
  end
39
70
  end
40
71
  end
41
72
 
42
- def_delegators 'self.class', :header_mappings, :value_mappings
73
+ def_delegators 'self.class', :mappings
43
74
 
44
75
  def initialize(table)
45
76
  self.table = table
@@ -53,20 +84,25 @@ module Cucumber
53
84
  @rows ||= table.hashes.map { |h| new_row(h) }
54
85
  end
55
86
 
87
+ def single_row
88
+ @single_row ||= new_row(table.rows_hash)
89
+ end
90
+
91
+ alias_method :to_a, :rows
92
+ alias_method :to_h, :single_row
93
+
56
94
  private
57
95
 
58
96
  attr_accessor :table
59
97
 
60
- def key_for(header)
61
- header_mappings[header]
62
- end
63
-
64
98
  def new_row(hash)
65
- hash.each_with_object({}) { |(k, v), h| h[key_for(k)] = value_for(k, v) }
99
+ hash.each_with_object({}) { |(k, v), h|
100
+ mapping_for(k).set(self, h, k, CellText.new(v))
101
+ }
66
102
  end
67
103
 
68
- def value_for(key, value)
69
- instance_exec(value, &value_mappings[key])
104
+ def mapping_for(header)
105
+ mappings[header]
70
106
  end
71
107
  end
72
108
  end
@@ -1,5 +1,5 @@
1
1
  module Cucumber
2
2
  module Salad
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -0,0 +1,9 @@
1
+ module Cucumber
2
+ module Salad
3
+ module Widgets
4
+ class Action < Atom
5
+ def_delegators :root, :click
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  module Cucumber
2
2
  module Salad
3
3
  module Widgets
4
- class Text < Widget
4
+ class Atom < Widget
5
5
  def to_a
6
6
  [to_s]
7
7
  end
@@ -11,7 +11,7 @@ module Cucumber
11
11
  end
12
12
 
13
13
  def to_s
14
- root.text.strip
14
+ node_text(root)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,66 @@
1
+ module Cucumber
2
+ module Salad
3
+ module Widgets
4
+ class AutoTable < BaseTable
5
+ protected
6
+
7
+ def ensure_table_loaded
8
+ root.find(data_row_selector)
9
+ rescue Capybara::Ambiguous
10
+ end
11
+
12
+ private
13
+
14
+ def data_cell_selector
15
+ 'td'
16
+ end
17
+
18
+ def data_row(node)
19
+ Row.new(root: node, cell_selector: data_cell_selector)
20
+ end
21
+
22
+ def data_row_selector
23
+ 'tbody tr'
24
+ end
25
+
26
+ def data_rows
27
+ @data_rows ||= root.all(data_row_selector).map { |n| data_row(n) }
28
+ end
29
+
30
+ def header_selector
31
+ 'thead th'
32
+ end
33
+
34
+ def headers
35
+ @headers ||= root.all(header_selector).map { |n| node_text(n).downcase }
36
+ end
37
+
38
+ def values
39
+ @values ||= data_rows.map(&:values)
40
+ end
41
+
42
+ class Row < Widget
43
+ def initialize(settings)
44
+ s = settings.dup
45
+
46
+ self.cell_selector = s.delete(:cell_selector)
47
+
48
+ super s
49
+ end
50
+
51
+ def values
52
+ root.all(cell_selector).map { |n| node_text(n) }
53
+ end
54
+
55
+ private
56
+
57
+ attr_accessor :cell_selector
58
+
59
+ def node_text(node)
60
+ NodeText.new(node)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,13 @@
1
+ module Cucumber
2
+ module Salad
3
+ module Widgets
4
+ class BaseTable < Widget
5
+ def to_table
6
+ ensure_table_loaded
7
+
8
+ headers.any? ? [headers, *values] : values
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,9 +2,15 @@ module Cucumber
2
2
  module Salad
3
3
  module Widgets
4
4
  class Form < Widget
5
+ def self.default_locator(type = nil, &block)
6
+ alias_method :name_to_locator, type if type
7
+
8
+ define_method :name_to_locator, &block if block
9
+ end
10
+
5
11
  def self.check_box(name, label = nil)
6
12
  define_method "#{name}=" do |val|
7
- l = label || name_to_label(name)
13
+ l = label || name_to_locator(name)
8
14
 
9
15
  if val
10
16
  root.check l
@@ -19,7 +25,7 @@ module Cucumber
19
25
  label, = args
20
26
 
21
27
  define_method "#{name}=" do |val|
22
- l = label || name_to_label(name)
28
+ l = label || name_to_locator(name)
23
29
  w = opts.fetch(:writer) { ->(v) { v } }
24
30
 
25
31
  root.select w.(val).to_s, from: l
@@ -32,14 +38,19 @@ module Cucumber
32
38
 
33
39
  def self.text(name, label = nil)
34
40
  define_method "#{name}=" do |val|
35
- l = label || name_to_label(name)
41
+ l = label || name_to_locator(name)
36
42
 
37
43
  root.fill_in l, with: val.to_s
38
44
  end
39
45
  end
40
46
 
41
- def initialize(*super_args)
42
- super(*super_args)
47
+ def initialize(settings = {})
48
+ s = settings.dup
49
+ data = s.delete(:data) || {}
50
+
51
+ super s
52
+
53
+ fill_all data
43
54
 
44
55
  if block_given?
45
56
  yield self
@@ -57,16 +68,20 @@ module Cucumber
57
68
  end
58
69
 
59
70
  def submit
60
- root.find('[name = "commit"]').click
71
+ root.find('[type = "submit"]').click
61
72
 
62
73
  self
63
74
  end
64
75
 
65
76
  private
66
77
 
67
- def name_to_label(name)
78
+ def label(name)
68
79
  name.to_s.humanize
69
80
  end
81
+
82
+ def name_to_locator(name)
83
+ label(name)
84
+ end
70
85
  end
71
86
  end
72
87
  end
@@ -2,13 +2,11 @@ module Cucumber
2
2
  module Salad
3
3
  module Widgets
4
4
  class List < Widget
5
- DEFAULT_TYPE = Text
6
-
7
- extend Forwardable
5
+ DEFAULT_TYPE = Atom
8
6
 
9
7
  include Enumerable
10
8
 
11
- def_delegators :items, :size, :include?, :each, :empty?
9
+ def_delegators :items, :size, :include?, :each, :empty?, :first
12
10
 
13
11
  def self.item(selector, type = DEFAULT_TYPE, &item_for)
14
12
  define_method :item_selector do
@@ -19,7 +17,7 @@ module Cucumber
19
17
  define_method :item_for, &item_for
20
18
  else
21
19
  define_method :item_factory do
22
- lookup(type)
20
+ type
23
21
  end
24
22
  end
25
23
  end
@@ -1,79 +1,68 @@
1
1
  module Cucumber
2
2
  module Salad
3
3
  module Widgets
4
- class Table < Widget
5
- def self.row_class
6
- Row
7
- end
8
-
9
- def to_table
10
- [headers, *rows.map { |r| Array(r) }]
11
- end
4
+ class Table < BaseTable
5
+ class ColumnDefinition
6
+ attr_reader :header
7
+
8
+ def initialize(selector, header, transform)
9
+ self.selector = selector
10
+ self.header = header
11
+ self.transform = transform
12
+ end
12
13
 
13
- private
14
+ def ensure_loaded(container)
15
+ container.find(selector)
16
+ rescue Capybara::Ambiguous
17
+ end
14
18
 
15
- def header_for(node)
16
- node.text.strip.downcase
17
- end
19
+ def values(container)
20
+ container.all(selector).map { |n| transform.(node_text(n)).to_s }
21
+ end
18
22
 
19
- def header_selector
20
- 'thead th'
21
- end
23
+ private
22
24
 
23
- def headers
24
- items(header_selector, :header_for)
25
- end
25
+ attr_accessor :selector
26
+ attr_writer :header, :transform
26
27
 
27
- def items(selector, builder)
28
- root.all(selector).map { |e| send(builder, e) }
29
- end
28
+ def node_text(node)
29
+ NodeText.new(node)
30
+ end
30
31
 
31
- def row_factory
32
- self.class.row_class
32
+ def transform
33
+ @transform ||= ->(v) { v }
34
+ end
33
35
  end
34
36
 
35
- def row_for(node)
36
- row_factory.new(root: node)
37
+ class << self
38
+ attr_accessor :column_selector, :header_selector
37
39
  end
38
40
 
39
- def row_selector
40
- 'tbody tr'
41
+ def self.column(selector, header = nil, &transform)
42
+ column_definitions << ColumnDefinition.new(selector, header, transform)
41
43
  end
42
44
 
43
- def rows
44
- items(row_selector, :row_for)
45
+ def self.column_definitions
46
+ @column_definitions ||= []
45
47
  end
46
48
 
47
- class Row < Widget
48
- def self.cell(name, selector, type = Text, &block)
49
- widget name, selector, type, &block
49
+ protected
50
50
 
51
- cells << name
52
- end
53
-
54
- def self.cells
55
- @cells ||= []
56
- end
57
-
58
- def to_a
59
- declared_row || generated_row
60
- end
61
-
62
- protected
51
+ def ensure_table_loaded
52
+ column_definitions.first.ensure_loaded(self)
53
+ end
63
54
 
64
- def cell_selector
65
- 'td'
66
- end
55
+ private
67
56
 
68
- def declared_row
69
- cells = self.class.cells
57
+ def_delegators 'self.class', :column_selector, :column_definitions,
58
+ :header_selector
70
59
 
71
- cells.map { |c| send(c).to_s } if cells.present?
72
- end
60
+ def headers
61
+ @headers ||= column_definitions.map(&:header)
62
+ end
73
63
 
74
- def generated_row
75
- root.all(cell_selector).map { |c| c.text.strip }
76
- end
64
+ def values
65
+ @values ||= column_definitions.map { |d| d.values(root) }.transpose
77
66
  end
78
67
  end
79
68
  end
@@ -2,8 +2,20 @@ module Cucumber
2
2
  module Salad
3
3
  module Widgets
4
4
  class Widget
5
+ extend Forwardable
6
+
5
7
  include Salad::Conversions
6
8
 
9
+ def self.action(name, selector, options = {})
10
+ wname = "#{name}_action"
11
+
12
+ widget wname, selector, type: options[:type] || Action
13
+
14
+ define_method name do
15
+ send(wname).click
16
+ end
17
+ end
18
+
7
19
  def self.root(selector)
8
20
  define_method :default_root_selector do
9
21
  selector
@@ -12,14 +24,13 @@ module Cucumber
12
24
  private :default_root_selector
13
25
  end
14
26
 
15
- def self.widget(name, selector, type = Text, &block)
16
- body = if block_given?
17
- ->{ block.(find(selector)) }
18
- else
19
- ->{ lookup(type).new(root: root.find(selector)) }
20
- end
27
+ def self.widget(name, selector, options = {}, &block)
28
+ type = options.fetch(:type, Atom)
29
+ t = block_given? ? Class.new(type, &block) : type
21
30
 
22
- define_method name, &body
31
+ define_method name do
32
+ t.new(root: root.find(selector))
33
+ end
23
34
  end
24
35
 
25
36
  def initialize(settings = {})
@@ -33,6 +44,12 @@ module Cucumber
33
44
  Nokogiri::XML(xml, &:noblanks).to_xhtml
34
45
  end
35
46
 
47
+ protected
48
+
49
+ def node_text(node)
50
+ NodeText.new(node)
51
+ end
52
+
36
53
  private
37
54
 
38
55
  attr_writer :root_selector
@@ -42,14 +59,6 @@ module Cucumber
42
59
  "#{self.class.name}: default root selector undefined"
43
60
  end
44
61
 
45
- def lookup(type)
46
- if Class === type
47
- type
48
- else
49
- self.class.const_get(type.to_s.classify)
50
- end
51
- end
52
-
53
62
  def page
54
63
  Capybara.current_session
55
64
  end
@@ -1,3 +1,6 @@
1
1
  require 'cucumber/salad/widgets/widget'
2
- require 'cucumber/salad/widgets/text'
2
+ require 'cucumber/salad/widgets/atom'
3
3
  require 'cucumber/salad/widgets/list'
4
+ require 'cucumber/salad/widgets/base_table'
5
+ require 'cucumber/salad/widgets/auto_table'
6
+ require 'cucumber/salad/widgets/table'
@@ -1,5 +1,11 @@
1
1
  require 'chronic'
2
2
 
3
3
  require 'cucumber/salad/conversions'
4
+ require 'cucumber/salad/instance_conversions'
5
+ require 'cucumber/salad/node_text'
4
6
  require 'cucumber/salad/widgets'
5
7
  require 'cucumber/salad/table'
8
+ require 'cucumber/salad/table/mapping'
9
+ require 'cucumber/salad/table/void_mapping'
10
+ require 'cucumber/salad/table/transformations'
11
+ require 'cucumber/salad/table/cell_text'
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: cucumber-salad
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.0
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - David Leal
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-20 00:00:00.000000000 Z
12
+ date: 2013-05-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,6 +43,22 @@ dependencies:
43
43
  - !ruby/object:Gem::Version
44
44
  version: '0'
45
45
  none: false
46
+ - !ruby/object:Gem::Dependency
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '2.0'
52
+ none: false
53
+ name: capybara
54
+ type: :runtime
55
+ prerelease: false
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ none: false
46
62
  - !ruby/object:Gem::Dependency
47
63
  version_requirements: !ruby/object:Gem::Requirement
48
64
  requirements:
@@ -68,13 +84,22 @@ extra_rdoc_files: []
68
84
  files:
69
85
  - lib/cucumber/salad.rb
70
86
  - lib/cucumber/salad/conversions.rb
87
+ - lib/cucumber/salad/instance_conversions.rb
88
+ - lib/cucumber/salad/node_text.rb
71
89
  - lib/cucumber/salad/table.rb
90
+ - lib/cucumber/salad/table/cell_text.rb
91
+ - lib/cucumber/salad/table/mapping.rb
92
+ - lib/cucumber/salad/table/transformations.rb
93
+ - lib/cucumber/salad/table/void_mapping.rb
72
94
  - lib/cucumber/salad/version.rb
73
95
  - lib/cucumber/salad/widgets.rb
96
+ - lib/cucumber/salad/widgets/action.rb
97
+ - lib/cucumber/salad/widgets/atom.rb
98
+ - lib/cucumber/salad/widgets/auto_table.rb
99
+ - lib/cucumber/salad/widgets/base_table.rb
74
100
  - lib/cucumber/salad/widgets/form.rb
75
101
  - lib/cucumber/salad/widgets/list.rb
76
102
  - lib/cucumber/salad/widgets/table.rb
77
- - lib/cucumber/salad/widgets/text.rb
78
103
  - lib/cucumber/salad/widgets/widget.rb
79
104
  homepage: https://github.com/mojotech/cucumber-salad
80
105
  licenses: []