storexplore 0.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.gitignore +26 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE +165 -0
  8. data/README.md +29 -0
  9. data/Rakefile +1 -0
  10. data/lib/storexplore/api.rb +63 -0
  11. data/lib/storexplore/api_builder.rb +68 -0
  12. data/lib/storexplore/array_utils.rb +36 -0
  13. data/lib/storexplore/browsing_error.rb +26 -0
  14. data/lib/storexplore/digger.rb +35 -0
  15. data/lib/storexplore/hash_utils.rb +56 -0
  16. data/lib/storexplore/null_digger.rb +30 -0
  17. data/lib/storexplore/testing/api_shared_examples.rb +140 -0
  18. data/lib/storexplore/testing/configuration.rb +56 -0
  19. data/lib/storexplore/testing/dummy_data.rb +67 -0
  20. data/lib/storexplore/testing/dummy_store.rb +195 -0
  21. data/lib/storexplore/testing/dummy_store_api.rb +54 -0
  22. data/lib/storexplore/testing/dummy_store_constants.rb +31 -0
  23. data/lib/storexplore/testing/dummy_store_generator.rb +65 -0
  24. data/lib/storexplore/testing/matchers/have_unique_matcher.rb +74 -0
  25. data/lib/storexplore/testing/matchers/mostly_matcher.rb +45 -0
  26. data/lib/storexplore/testing.rb +30 -0
  27. data/lib/storexplore/uri_utils.rb +38 -0
  28. data/lib/storexplore/version.rb +24 -0
  29. data/lib/storexplore/walker.rb +84 -0
  30. data/lib/storexplore/walker_page.rb +142 -0
  31. data/lib/storexplore/walker_page_error.rb +25 -0
  32. data/lib/storexplore.rb +34 -0
  33. data/spec/lib/storexplore/api_builder_spec.rb +99 -0
  34. data/spec/lib/storexplore/api_spec.rb +44 -0
  35. data/spec/lib/storexplore/digger_spec.rb +53 -0
  36. data/spec/lib/storexplore/store_walker_page_spec_fixture.html +21 -0
  37. data/spec/lib/storexplore/testing/dummy_store_api_spec.rb +120 -0
  38. data/spec/lib/storexplore/uri_utils_spec.rb +51 -0
  39. data/spec/lib/storexplore/walker_page_spec.rb +120 -0
  40. data/spec/lib/storexplore/walker_spec.rb +97 -0
  41. data/spec/spec_helper.rb +28 -0
  42. data/storexplore.gemspec +27 -0
  43. data.tar.gz.sig +0 -0
  44. metadata +187 -0
  45. metadata.gz.sig +0 -0
@@ -0,0 +1,140 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # api_shared_examples.rb
4
+ #
5
+ # Copyright (c) 2010, 2011, 2012, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ module Storexplore
23
+ module Testing
24
+
25
+ module ApiSpecMacros
26
+
27
+ def self.included(base)
28
+ base.send :extend, ClassMethods
29
+ end
30
+
31
+ module ClassMethods
32
+
33
+ def it_should_behave_like_any_store_items_api
34
+
35
+ before :all do
36
+ @range = 0..1
37
+
38
+ generate_store
39
+ explore_store
40
+ end
41
+
42
+ it "should have many item categories" do
43
+ # TODO remove .first(3) once rspec handles lazy enums
44
+ expect(@store.categories.first(3)).to have_at_least(3).items
45
+ end
46
+
47
+ it "should have many items" do
48
+ expect(sample_items).to have_at_least(3).items
49
+ end
50
+
51
+ it "should have item categories with different names" do
52
+ categories_attributes = sample_categories.map { |cat| cat.attributes }
53
+ expect(categories_attributes).to mostly have_unique(:name).in(categories_attributes)
54
+ end
55
+
56
+ it "should have items with different names" do
57
+ expect(sample_items_attributes).to mostly have_unique(:name).in(sample_items_attributes)
58
+ end
59
+
60
+ it "should have parseable item category attributes" do
61
+ expect(parseable_categories_attributes).to mostly have_key(:name)
62
+ end
63
+
64
+ it "should have some valid item attributes" do
65
+ expect(sample_items_attributes).not_to be_empty
66
+ end
67
+
68
+ it "should have items with a price" do
69
+ expect(sample_items_attributes).to all_ { have_key(:price) }
70
+ end
71
+
72
+ it "should mostly have items with an image" do
73
+ expect(sample_items_attributes).to mostly have_key(:image)
74
+ end
75
+
76
+ it "should mostly have items with a brand" do
77
+ expect(sample_items_attributes).to mostly have_key(:brand)
78
+ end
79
+
80
+ it "should have items with unique remote id" do
81
+ expect(sample_items_attributes).to all_ { have_unique(:remote_id).in(sample_items_attributes) }
82
+ end
83
+
84
+ it "should have items with unique uris" do
85
+ expect(valid_sample_items).to mostly have_unique(:uri).in(valid_sample_items)
86
+ end
87
+ end
88
+ end
89
+
90
+ def generate_store
91
+ # by default, the store already exists
92
+ end
93
+
94
+ def explore_store
95
+ @sample_categories = dig([@store], :categories)
96
+ @all_sample_categories, @sample_items = dig_deep(@sample_categories)
97
+ @valid_sample_items = valid_items(@sample_items)
98
+ @sample_items_attributes = (valid_sample_items.map &:attributes).uniq
99
+ @parseable_categories_attributes = all_sample_categories.map do |category|
100
+ category.attributes rescue {}
101
+ end
102
+ end
103
+
104
+ attr_accessor :sample_categories, :all_sample_categories, :sample_items, :valid_sample_items, :sample_items_attributes, :parseable_categories_attributes
105
+
106
+ def dig(categories, message)
107
+ categories.map { |cat| cat.send(message).to_a[@range] }.flatten
108
+ end
109
+
110
+ def dig_deep(categories)
111
+ all_categories = [categories]
112
+
113
+ while (items = dig(categories, :items)).empty?
114
+ categories = dig(categories, :categories)
115
+ all_categories << categories
116
+ end
117
+
118
+ [all_categories.flatten, items]
119
+ end
120
+
121
+ def valid_items(items)
122
+ result = []
123
+ sample_items.each do |item|
124
+ begin
125
+ item.attributes
126
+ result.push(item)
127
+ rescue BrowsingError => e
128
+ Testing.logger.debug e.message
129
+ end
130
+ end
131
+ result
132
+ end
133
+
134
+ def logger
135
+ Testing.config.logger
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,56 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # configuration.rb
4
+ #
5
+ # Copyright (c) 2010, 2011, 2012, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ require 'logger'
23
+
24
+ module Storexplore
25
+ module Testing
26
+
27
+ def self.config
28
+ @config ||= Configuration.new
29
+ yield @config if block_given?
30
+ @config
31
+ end
32
+
33
+ class Configuration
34
+
35
+ def initialize
36
+ @logger = Logger.new(STDOUT)
37
+ @logger.level = Logger::INFO
38
+ end
39
+
40
+ # Generation directory where the dummy stores will be generated.
41
+ # A sub folder with name DummyStore::NAME will be created there to hold all generated dummy stores,
42
+ # the content of this directory will be deleted when the DummyStore.wipeout method is called
43
+ def dummy_store_generation_dir
44
+ raise StandardError.new('You need to configure a dummy store generation directory with Storexplore::Testing.config.dummy_store_generation_dir=') if @generation_dir.nil?
45
+ @generation_dir
46
+ end
47
+ def dummy_store_generation_dir=(generation_dir)
48
+ @generation_dir = generation_dir
49
+ end
50
+
51
+ # Logger for custom test messages
52
+ attr_accessor :logger
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # dummy_data.rb
4
+ #
5
+ # Copyright (c) 2012, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ module Storexplore
23
+ module Testing
24
+
25
+ class DummyData
26
+
27
+ def self.name(kind)
28
+ "#{kind.capitalize}-#{new_int}"
29
+ end
30
+
31
+ def self.attributes(name, options)
32
+ {
33
+ name: name,
34
+ brand: brand(name),
35
+ image: image(name),
36
+ remote_id: remote_id,
37
+ price: price(name)
38
+ }.merge(options)
39
+ end
40
+
41
+ private
42
+
43
+ def self.brand(name)
44
+ "#{name} Inc."
45
+ end
46
+ def self.image(name)
47
+ "http://www.photofabric.com/#{name}"
48
+ end
49
+ def self.remote_id
50
+ new_int.to_s
51
+ end
52
+ def self.price(name)
53
+ hash = name.hash.abs
54
+ digits = Math.log(hash, 10).round
55
+ (hash/10.0**(digits-2)).round(2)
56
+ end
57
+
58
+ def self.new_int
59
+ @last_int ||= 0
60
+ result = @last_int
61
+ @last_int = @last_int + 1
62
+ result
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,195 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # dummy_store.rb
4
+ #
5
+ # Copyright (c) 2012, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ require "fileutils"
23
+
24
+ module Storexplore
25
+ module Testing
26
+
27
+ class DummyStore
28
+
29
+ def self.open(store_name)
30
+ new(root_path(store_name), store_name)
31
+ end
32
+
33
+ def self.uri(store_name)
34
+ "file://#{root_path(store_name)}"
35
+ end
36
+
37
+ def self.wipe_out
38
+ FileUtils.rm_rf(root_dir)
39
+ end
40
+ def self.wipe_out_store(store_name)
41
+ FileUtils.rm_rf(root_path(store_name))
42
+ end
43
+
44
+ def uri
45
+ "file://#{@path}"
46
+ end
47
+
48
+ attr_reader :name
49
+
50
+ def categories
51
+ _name, categories, _items, _attributes = read
52
+ categories.map do |category_name|
53
+ DummyStore.new("#{absolute_category_dir(category_name)}/index.html", category_name)
54
+ end
55
+ end
56
+ def category(category_name)
57
+ short_category_name = short_name(category_name)
58
+ add([short_category_name], [], {})
59
+ DummyStore.new("#{absolute_category_dir(short_category_name)}/index.html", category_name)
60
+ end
61
+ def remove_category(category_name)
62
+ short_category_name = short_name(category_name)
63
+ remove([short_category_name], [], [])
64
+ FileUtils.rm_rf(absolute_category_dir(short_category_name))
65
+ end
66
+
67
+ def items
68
+ _name, _categories, items, _attributes = read
69
+ items.map do |item_name|
70
+ DummyStore.new(absolute_item_file(item_name), item_name)
71
+ end
72
+ end
73
+ def item(item_name)
74
+ short_item_name = short_name(item_name)
75
+ add([], [short_item_name], {})
76
+ DummyStore.new(absolute_item_file(short_item_name), item_name)
77
+ end
78
+ def remove_item(item_name)
79
+ short_item_name = short_name(item_name)
80
+ remove([], [short_item_name], [])
81
+ FileUtils.rm_rf(absolute_item_file(short_item_name))
82
+ end
83
+
84
+ def attributes(*args)
85
+ return add_attributes(args[0]) if args.size == 1
86
+
87
+ _name, _categories, _items, attributes = read
88
+ HashUtils.internalize_keys(attributes)
89
+ end
90
+ def add_attributes(values)
91
+ add([], [], HashUtils.stringify_keys(values))
92
+ end
93
+ def remove_attributes(*attribute_names)
94
+ remove([], [], ArrayUtils.stringify(attribute_names))
95
+ end
96
+
97
+ def generate(count = 1)
98
+ DummyStoreGenerator.new([self], count)
99
+ end
100
+
101
+ private
102
+
103
+ def self.root_dir
104
+ File.join(Testing.config.dummy_store_generation_dir, DummyStoreConstants::NAME)
105
+ end
106
+
107
+ def self.root_path(store_name)
108
+ "#{root_dir}/#{store_name}/index.html"
109
+ end
110
+
111
+ def initialize(path, name)
112
+ @path = path
113
+ @name = name
114
+ if !File.exists?(path)
115
+ write(name, [], [], {})
116
+ end
117
+ end
118
+
119
+ def short_name(full_name)
120
+ full_name[0..20]
121
+ end
122
+
123
+ def absolute_category_dir(category_name)
124
+ "#{File.dirname(@path)}/#{relative_category_dir(category_name)}"
125
+ end
126
+ def relative_category_dir(category_name)
127
+ category_name
128
+ end
129
+
130
+ def absolute_item_file(item_name)
131
+ "#{File.dirname(@path)}/#{relative_item_file(item_name)}"
132
+ end
133
+ def relative_item_file(item_name)
134
+ "#{item_name}.html"
135
+ end
136
+
137
+ def add(extra_categories, extra_items, extra_attributes)
138
+ name, categories, items, attributes = read
139
+
140
+ if !ArrayUtils.contains?(categories, extra_categories) || !ArrayUtils.contains?(items, extra_items) || !HashUtils.contains?(attributes,extra_attributes)
141
+ write(name, categories + extra_categories, items + extra_items, attributes.merge(extra_attributes))
142
+ end
143
+ end
144
+ def remove(wrong_categories, wrong_items, wrong_attributes)
145
+ name, categories, items, attributes = read
146
+ write(name, categories - wrong_categories, items - wrong_items, HashUtils.without(attributes,wrong_attributes))
147
+ end
148
+
149
+ def write(name, categories, items, attributes)
150
+ FileUtils.mkdir_p(File.dirname(@path))
151
+ IO.write(@path, content(name, categories, items, attributes))
152
+ end
153
+
154
+ def content(name, categories, items, attributes)
155
+ (["<!DOCTYPE html>",
156
+ "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head>",
157
+ "<body><h1>#{name}</h1><div id=\"categories\"><h2>Categories</h2><ul>"] +
158
+ categories.map {|cat| "<li><a class=\"category\" href=\"#{relative_category_dir(cat)}/index.html\">#{cat}</a></li>" } +
159
+ ['</ul></div><div id="items"><h2>Items</h2><ul>']+
160
+ items.map {|item| "<li><a class=\"item\" href=\"#{relative_item_file(item)}\">#{item}</a></li>" } +
161
+ ['</ul></div><div id="attributes"><h2>Attributes</h2><ul>']+
162
+ attributes.map {|key, value| "<li><span id=\"#{key}\">#{value}</span></li>" } +
163
+ ["</ul></div></body></html>"]).join("\n")
164
+ end
165
+
166
+ def read
167
+ parse(IO.readlines(@path))
168
+ end
169
+
170
+ def parse(lines)
171
+ name = ""
172
+ categories = []
173
+ items = []
174
+ attributes = {}
175
+ lines.each do |line|
176
+ name_match = /<h1>([^<]+)<\/h1>/.match(line)
177
+ sub_match = /<li><a class=\"([^\"]+)\" href=\"[^\"]+.html\">([^<]+)<\/a><\/li>/.match(line)
178
+ attr_match = /<li><span id=\"([^\"]+)\">([^<]+)<\/span><\/li>/.match(line)
179
+
180
+ if !!name_match
181
+ name = name_match[1]
182
+ elsif !!sub_match
183
+ case sub_match[1]
184
+ when "category" then categories << sub_match[2]
185
+ when "item" then items << sub_match[2]
186
+ end
187
+ elsif !!attr_match
188
+ attributes[attr_match[1]] = attr_match[2]
189
+ end
190
+ end
191
+ [name, categories, items, attributes]
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # dummy_store_api.rb
4
+ #
5
+ # Copyright (c) 2012, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ require_relative 'dummy_store_constants'
23
+
24
+ module Storexplore
25
+ module Testing
26
+
27
+ Storexplore::define_api DummyStoreConstants::NAME do
28
+
29
+ categories 'a.category' do
30
+ attributes do
31
+ { :name => page.get_one("h1").content }
32
+ end
33
+
34
+ categories 'a.category' do
35
+ attributes do
36
+ { :name => page.get_one("h1").content }
37
+ end
38
+
39
+ items 'a.item' do
40
+ attributes do
41
+ {
42
+ :name => page.get_one('h1').content,
43
+ :brand => page.get_one('#brand').content,
44
+ :price => page.get_one('#price').content.to_f,
45
+ :image => page.get_one('#image').content,
46
+ :remote_id => page.get_one('#remote_id').content
47
+ }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # dummy_store_constants.rb
4
+ #
5
+ # Copyright (c) 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ module Storexplore
23
+ module Testing
24
+
25
+ class DummyStoreConstants
26
+
27
+ NAME = 'dummy-store'
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # dummy_store_generator.rb
4
+ #
5
+ # Copyright (c) 2012, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+ module Storexplore
23
+ module Testing
24
+
25
+ class DummyStoreGenerator
26
+ def initialize(pages, count = 1)
27
+ @pages = pages
28
+ @count = count
29
+ end
30
+
31
+ def and(count)
32
+ @count = count
33
+ self
34
+ end
35
+
36
+ def categories
37
+ dispatch(:category)
38
+ end
39
+ alias_method :category, :categories
40
+
41
+ def items
42
+ dispatch(:item).attributes
43
+ end
44
+ alias_method :item, :items
45
+
46
+ def attributes(options = {})
47
+ @pages.map do |page|
48
+ attributes = DummyData.attributes(page.name, options)
49
+ page.attributes(HashUtils.without(attributes, [:name]))
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def dispatch(message)
56
+ sub_pages = @pages.map do |page|
57
+ @count.times.map do
58
+ page.send(message, DummyData.name(message))
59
+ end
60
+ end
61
+ DummyStoreGenerator.new(sub_pages.flatten)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,74 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # have_unique_matcher.rb
4
+ #
5
+ # Copyright (c) 2010, 2011, 2013 by Philippe Bourgau. All rights reserved.
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License as published by the Free Software Foundation; either
10
+ # version 3.0 of the License, or (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public
18
+ # License along with this library; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
+ # MA 02110-1301 USA
21
+
22
+
23
+ # Matcher to verify that a hash's key is unique in a collection of other hashes
24
+ # a full class is required to implement the in method
25
+ # Use like: expect(hash).to have_unique(:id).in(hashes)
26
+ class HaveUnique
27
+
28
+ def initialize(key)
29
+ @key = key
30
+ end
31
+
32
+ def in(collection)
33
+ @collection = collection
34
+ @index = Hash.new(0)
35
+ collection.each do |item|
36
+ @index[value(item)] += 1
37
+ end
38
+ self
39
+ end
40
+
41
+ def matches?(actual)
42
+ @actual = actual
43
+ @index[value(actual)] == 1
44
+ end
45
+
46
+ def failure_message_for_should
47
+ "expected #{value_expression(actual)} (=#{value(actual)}) to be unique in #{@collection}"
48
+ end
49
+
50
+ def description
51
+ "expected an hash or object with a unique #{@key} in #{@collection}"
52
+ end
53
+
54
+ private
55
+ def value(actual)
56
+ if actual.instance_of?(Hash)
57
+ actual[@key]
58
+ else
59
+ actual.send(@key)
60
+ end
61
+ end
62
+ def value_expression(actual)
63
+ if actual.instance_of?(Hash)
64
+ "#{actual}[#{@key}]"
65
+ else
66
+ "#{actual}.#{@key}"
67
+ end
68
+ end
69
+ end
70
+
71
+ def have_unique(key)
72
+ HaveUnique.new(key)
73
+ end
74
+