cucumber-salad 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.
@@ -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: []