spree_analytics_trackers 1.1.0 → 2.0.0.rc1
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 +4 -4
- data/.rubocop.yml +2 -0
- data/.travis.yml +30 -36
- data/Appraisals +3 -16
- data/LICENSE +1 -1
- data/README.md +7 -3
- data/app/assets/javascripts/spree/frontend/add_to_cart_analytics.js +46 -0
- data/app/controllers/spree_analytics_trackers/store_controller_decorator.rb +6 -3
- data/app/helpers/spree/trackers_helper.rb +58 -20
- data/app/overrides/add_cart_viewed_to_orders_edit.rb +1 -1
- data/app/overrides/add_checkout_step_viewed_to_checkout_edit.rb +11 -3
- data/app/overrides/add_google_analytics_page_view_to_spree_application.rb +18 -0
- data/app/overrides/add_google_analytics_to_spree_application.rb +11 -3
- data/app/overrides/add_google_purchase_to_orders_show.rb +1 -1
- data/app/overrides/add_product_list_filtered_to_taxons_show.rb +1 -1
- data/app/overrides/add_product_list_viewed_to_products_list.rb +1 -1
- data/app/overrides/add_product_viewed_to_products_show.rb +2 -2
- data/app/overrides/add_products_searched_to_products_list.rb +2 -2
- data/app/overrides/add_segment_initializer_to_layout.rb +10 -2
- data/app/overrides/add_segment_page_viewed_to_layout.rb +18 -0
- data/app/presenters/spree_analytics_trackers/base_presenter.rb +14 -0
- data/app/presenters/spree_analytics_trackers/segment/order_presenter.rb +32 -0
- data/app/presenters/spree_analytics_trackers/segment/product_presenter.rb +40 -0
- data/app/views/spree/shared/trackers/google_analytics/_checkout_step_viewed.js.erb +21 -0
- data/app/views/spree/shared/trackers/google_analytics/_initializer.js.erb +11 -0
- data/app/views/spree/shared/trackers/google_analytics/_page_viewed.js.erb +13 -0
- data/app/views/spree/shared/trackers/google_analytics/_purchase.js.erb +19 -0
- data/app/views/spree/shared/trackers/segment/_cart_viewed.js.erb +21 -21
- data/app/views/spree/shared/trackers/segment/_checkout_step_viewed.js.erb +14 -12
- data/app/views/spree/shared/trackers/segment/_initializer.js.erb +1 -2
- data/app/views/spree/shared/trackers/segment/_order_complete.js.erb +3 -43
- data/app/views/spree/shared/trackers/segment/_page_viewed.js.erb +7 -0
- data/app/views/spree/shared/trackers/segment/_product_list_filtered.js.erb +7 -18
- data/app/views/spree/shared/trackers/segment/_product_list_viewed.js.erb +16 -21
- data/app/views/spree/shared/trackers/segment/_product_viewed.js.erb +10 -6
- data/app/views/spree/shared/trackers/segment/_products_searched.js.erb +1 -1
- data/config/locales/en.yml +0 -2
- data/config/locales/zh-TW.yml +19 -0
- data/gemfiles/{spree_3_5.gemfile → spree_4_1.gemfile} +2 -2
- data/gemfiles/spree_master.gemfile +0 -1
- data/lib/generators/spree_analytics_trackers/install/install_generator.rb +7 -0
- data/lib/spree_analytics_trackers.rb +1 -0
- data/lib/spree_analytics_trackers/version.rb +4 -3
- data/spec/features/admin/configuration/analytics_tracker_spec.rb +3 -1
- data/spec/features/products_spec.rb +315 -0
- data/spec/fixtures/thinking-cat.jpg +0 -0
- data/spec/presenters/spree_analytics_trackers/segment/order_presenter_spec.rb +34 -0
- data/spec/spec_helper.rb +7 -82
- data/spec/support/custom_products.rb +26 -0
- data/spree_analytics_trackers.gemspec +3 -17
- metadata +36 -220
- data/app/overrides/add_google_checkout_to_checkout_edit.rb +0 -9
- data/app/overrides/add_product_added_to_orders_edit.rb +0 -10
- data/app/views/spree/shared/_google_add_items.html.erb +0 -13
- data/app/views/spree/shared/_google_analytics.html.erb +0 -18
- data/app/views/spree/shared/_google_checkout.html.erb +0 -14
- data/app/views/spree/shared/_google_purchase.html.erb +0 -19
- data/app/views/spree/shared/trackers/segment/_product_added.js.erb +0 -9
- data/gemfiles/spree_3_7.gemfile +0 -10
- data/gemfiles/spree_4_0.gemfile +0 -9
data/config/locales/en.yml
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
zh-TW:
|
2
|
+
activerecord:
|
3
|
+
attributes:
|
4
|
+
spree/tracker:
|
5
|
+
one: 追蹤器
|
6
|
+
other: 追蹤器
|
7
|
+
spree:
|
8
|
+
analytics_engine: 分析引擎
|
9
|
+
analytics_trackers: 分析追蹤器
|
10
|
+
new_tracker: 新增追蹤器
|
11
|
+
google_analytics: Google 分析
|
12
|
+
google_analytics_id: Google 追蹤器代碼
|
13
|
+
analytics_desc_header_1: Spree 分析
|
14
|
+
analytics_desc_header_2: 新增即時分析到 Spree 的後台
|
15
|
+
analytics_desc_list_1: 獲得即時銷售數據
|
16
|
+
analytics_desc_list_2: 免費安裝
|
17
|
+
analytics_desc_list_3: 不需要寫任何代碼安裝
|
18
|
+
analytics_desc_list_4: 完全免費!
|
19
|
+
|
@@ -3,6 +3,13 @@ module SpreeAnalyticsTrackers
|
|
3
3
|
class InstallGenerator < Rails::Generators::Base
|
4
4
|
class_option :auto_run_migrations, type: :boolean, default: false
|
5
5
|
|
6
|
+
def add_javascripts
|
7
|
+
javascripts_path = 'vendor/assets/javascripts/spree/frontend/all.js'
|
8
|
+
return unless File.file?(javascripts_path)
|
9
|
+
|
10
|
+
append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/add_to_cart_analytics\n"
|
11
|
+
end
|
12
|
+
|
6
13
|
def add_migrations
|
7
14
|
run 'bundle exec rake railties:install:migrations FROM=spree_analytics_trackers'
|
8
15
|
end
|
@@ -8,10 +8,11 @@ module SpreeAnalyticsTrackers
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module VERSION
|
11
|
-
MAJOR =
|
12
|
-
MINOR =
|
11
|
+
MAJOR = 2
|
12
|
+
MINOR = 0
|
13
13
|
TINY = 0
|
14
|
+
PRE = 'rc1'
|
14
15
|
|
15
|
-
STRING = [MAJOR, MINOR, TINY].compact.join('.')
|
16
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
16
17
|
end
|
17
18
|
end
|
@@ -35,7 +35,9 @@ describe 'Analytics Tracker', type: :feature do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'should be able to create a new analytics tracker' do
|
38
|
-
|
38
|
+
within('.content-header') do
|
39
|
+
click_link 'admin_new_tracker_link'
|
40
|
+
end
|
39
41
|
fill_in 'tracker_analytics_id', with: 'A100'
|
40
42
|
click_button 'Create'
|
41
43
|
|
@@ -0,0 +1,315 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Visiting Products', type: :feature, js: true do
|
5
|
+
include_context 'custom products'
|
6
|
+
|
7
|
+
let(:store) { Spree::Store.default }
|
8
|
+
|
9
|
+
let(:store_name) do
|
10
|
+
((first_store = Spree::Store.first) && first_store.name).to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
let!(:ga_tracker) { create(:tracker) }
|
14
|
+
let!(:segment_tracker) { create(:tracker, engine: :segment) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
visit spree.products_path
|
18
|
+
allow(ENV).to receive(:[]).and_call_original
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'is able to show the shopping cart after adding a product to it' do
|
22
|
+
click_link 'Ruby on Rails Ringer T-Shirt'
|
23
|
+
expect(page).to have_content('$159.99')
|
24
|
+
|
25
|
+
expect(page).to have_selector('form#add-to-cart-form')
|
26
|
+
expect(page).to have_selector(:button, id: 'add-to-cart-button', disabled: false)
|
27
|
+
click_button 'add-to-cart-button'
|
28
|
+
expect(page).to have_content(Spree.t(:added_to_cart))
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'using Russian Rubles as a currency' do
|
32
|
+
before do
|
33
|
+
store.update(default_currency: 'RUB')
|
34
|
+
end
|
35
|
+
|
36
|
+
let!(:product) do
|
37
|
+
product = Spree::Product.find_by(name: 'Ruby on Rails Ringer T-Shirt')
|
38
|
+
product.master.prices.create(amount: 19.99, currency: 'RUB')
|
39
|
+
product.tap(&:save)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Regression tests for #2737
|
43
|
+
context 'uses руб as the currency symbol' do
|
44
|
+
it 'on products page' do
|
45
|
+
visit spree.products_path
|
46
|
+
within("#product_#{product.id}") do
|
47
|
+
within('.product-component-price') do
|
48
|
+
expect(page).to have_content('19.99 ₽')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'on product page' do
|
54
|
+
visit spree.product_path(product)
|
55
|
+
within('.price') do
|
56
|
+
expect(page).to have_content('19.99 ₽')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'when adding a product to the cart' do
|
61
|
+
add_to_cart(product)
|
62
|
+
|
63
|
+
within('.shopping-cart-total-amount') do
|
64
|
+
expect(page).to have_content('19.99 ₽')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'is able to search for a product' do
|
71
|
+
find('.search-icons').click
|
72
|
+
fill_in 'keywords', with: 'shirt'
|
73
|
+
|
74
|
+
if Spree.version.to_f > 4.1
|
75
|
+
first('button[type=submit]').click
|
76
|
+
else
|
77
|
+
find('#search-dropdown label').click
|
78
|
+
end
|
79
|
+
|
80
|
+
expect(page).to have_css('.product-component-name').once
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'a product with variants' do
|
84
|
+
let(:product) { Spree::Product.find_by(name: 'Ruby on Rails Baseball Jersey') }
|
85
|
+
let(:option_value) { create(:option_value) }
|
86
|
+
let!(:variant) { build(:variant, price: 5.59, product: product, option_values: []) }
|
87
|
+
|
88
|
+
before do
|
89
|
+
image = File.open(File.expand_path('../fixtures/thinking-cat.jpg', __dir__))
|
90
|
+
create_image(product, image)
|
91
|
+
|
92
|
+
product.option_types << option_value.option_type
|
93
|
+
variant.option_values << option_value
|
94
|
+
variant.save!
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is displayed' do
|
98
|
+
expect { click_link product.name }.not_to raise_error
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'displays price of first variant listed' do
|
102
|
+
click_link product.name
|
103
|
+
within('#product-price') do
|
104
|
+
expect(page).to have_content variant.price
|
105
|
+
expect(page).not_to have_content Spree.t(:out_of_stock)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "doesn't display out of stock for master product" do
|
110
|
+
product.master.stock_items.update_all count_on_hand: 0, backorderable: false
|
111
|
+
|
112
|
+
click_link product.name
|
113
|
+
within('#product-price') do
|
114
|
+
expect(page).not_to have_content Spree.t(:out_of_stock)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "doesn't display cart form if all variants (including master) are out of stock" do
|
119
|
+
product.variants_including_master.each { |v| v.stock_items.update_all count_on_hand: 0, backorderable: false }
|
120
|
+
|
121
|
+
click_link product.name
|
122
|
+
within('[data-hook=product_price]') do
|
123
|
+
expect(page).not_to have_content Spree.t(:add_to_cart)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'a product with variants, images only for the variants' do
|
129
|
+
let(:product) { Spree::Product.find_by(name: 'Ruby on Rails Baseball Jersey') }
|
130
|
+
let(:variant1) { create(:variant, product: product, price: 9.99) }
|
131
|
+
let(:variant2) { create(:variant, product: product, price: 10.99) }
|
132
|
+
|
133
|
+
before do
|
134
|
+
image = File.open(File.expand_path('../fixtures/thinking-cat.jpg', __dir__))
|
135
|
+
create_image(variant1, image)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'does not display no image available' do
|
139
|
+
visit spree.products_path
|
140
|
+
expect(page).to have_selector("img[data-src$='thinking-cat.jpg']")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'an out of stock product without variants' do
|
145
|
+
let(:product) { Spree::Product.find_by(name: 'Ruby on Rails Tote') }
|
146
|
+
|
147
|
+
before do
|
148
|
+
product.master.stock_items.update_all count_on_hand: 0, backorderable: false
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'does display out of stock for master product' do
|
152
|
+
click_link product.name
|
153
|
+
within('#inside-product-cart-form') do
|
154
|
+
expect(page).to have_content Spree.t(:out_of_stock).upcase
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it "doesn't display cart form if master is out of stock" do
|
159
|
+
click_link product.name
|
160
|
+
within('[data-hook=product_price]') do
|
161
|
+
expect(page).not_to have_content Spree.t(:add_to_cart)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'pagination' do
|
167
|
+
before { Spree::Config.products_per_page = 3 }
|
168
|
+
|
169
|
+
it 'is able to display products priced between 151 and 200 dollars across multiple pages' do
|
170
|
+
find(:css, '#filtersPrice').click
|
171
|
+
within(:css, '#collapseFilterPrice') { click_on '$151 - $200' }
|
172
|
+
expect(page).to have_css('.product-component-name').exactly(3).times
|
173
|
+
next_page = find_all(:css, '.next_page')
|
174
|
+
within(next_page[0]) { find(:css, '.page-link').click }
|
175
|
+
expect(page).to have_css('.product-component-name').once
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'returns the correct title when displaying a single product' do
|
180
|
+
product = Spree::Product.find_by(name: 'Ruby on Rails Baseball Jersey')
|
181
|
+
click_link product.name
|
182
|
+
|
183
|
+
within('div#product-description') do
|
184
|
+
within('h1.product-details-title') do
|
185
|
+
expect(page).to have_content('Ruby on Rails Baseball Jersey')
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'product is on sale' do
|
191
|
+
let(:product) do
|
192
|
+
FactoryBot.create(:base_product, description: 'Testing sample', name: 'Sample', price: '19.99')
|
193
|
+
end
|
194
|
+
let(:option_type) { create(:option_type) }
|
195
|
+
let(:option_value1) { create(:option_value, name: 'small', presentation: 'S', option_type: option_type) }
|
196
|
+
let(:option_value2) { create(:option_value, name: 'medium', presentation: 'M', option_type: option_type) }
|
197
|
+
let(:option_value3) { create(:option_value, name: 'large', presentation: 'L', option_type: option_type) }
|
198
|
+
let(:variant1) { create(:variant, product: product, option_values: [option_value1], price: '49.99') }
|
199
|
+
let(:variant2) { create(:variant, product: product, option_values: [option_value2], price: '69.99') }
|
200
|
+
let(:variant3) { create(:variant, product: product, option_values: [option_value3], price: '89.99') }
|
201
|
+
|
202
|
+
before do
|
203
|
+
if Spree.version.to_f > 4.1
|
204
|
+
price1 = Spree::Price.find_by(variant: variant1)
|
205
|
+
price2 = Spree::Price.find_by(variant: variant2)
|
206
|
+
price3 = Spree::Price.find_by(variant: variant3)
|
207
|
+
price1.update(compare_at_amount: '149.99')
|
208
|
+
price2.update(compare_at_amount: '169.99')
|
209
|
+
price3.update(compare_at_amount: '79.99')
|
210
|
+
price1.save
|
211
|
+
price2.save
|
212
|
+
price3.save
|
213
|
+
|
214
|
+
product.master.prices.first.update(compare_at_amount: 29.99)
|
215
|
+
end
|
216
|
+
|
217
|
+
product.master.stock_items.update_all count_on_hand: 10, backorderable: true
|
218
|
+
product.option_types << option_type
|
219
|
+
product.variants << [variant1, variant2, variant3]
|
220
|
+
product.tap(&:save)
|
221
|
+
|
222
|
+
visit spree.product_path(product)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'shows pre sales price on PDP' do
|
226
|
+
expect(page).to have_content('$49.99')
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'shows both pre sales and current prices on PDP' do
|
230
|
+
if Spree.version.to_f > 4.1
|
231
|
+
expect(page).to have_content('$49.99')
|
232
|
+
expect(page).to have_content('$149.99')
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'shows prices for other variants' do
|
237
|
+
within('.product-variants-variant-values') do
|
238
|
+
find('li', text: 'M').click
|
239
|
+
end
|
240
|
+
|
241
|
+
expect(page).to have_content('$69.99')
|
242
|
+
|
243
|
+
if Spree.version.to_f > 4.1
|
244
|
+
expect(page).to have_content('$169.99')
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'does not show pre sales price when it is bigger than price' do
|
249
|
+
if Spree.version.to_f > 4.1
|
250
|
+
within('.product-variants-variant-values') do
|
251
|
+
find('li', text: 'L').click
|
252
|
+
end
|
253
|
+
|
254
|
+
expect(page).to have_content('$89.99')
|
255
|
+
expect(page).not_to have_content('$79.99')
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
xdescribe 'When Requesting A Product By Variant Using URL Query String' do
|
261
|
+
let(:product) do
|
262
|
+
FactoryBot.create(:base_product, description: 'Testing sample', name: 'Sample', price: '19.99')
|
263
|
+
end
|
264
|
+
|
265
|
+
let(:option_type) { create(:option_type) }
|
266
|
+
let(:option_value1) { create(:option_value, name: 'small', presentation: 'S', option_type: option_type) }
|
267
|
+
let(:option_value2) { create(:option_value, name: 'medium', presentation: 'M', option_type: option_type) }
|
268
|
+
let(:option_value3) { create(:option_value, name: 'large', presentation: 'L', option_type: option_type) }
|
269
|
+
let(:variant1) { create(:variant, product: product, option_values: [option_value1], price: '49.99', sku: 'VAR-1') }
|
270
|
+
let(:variant2) { create(:variant, product: product, option_values: [option_value2], price: '69.99', sku: 'VAR-2') }
|
271
|
+
let(:variant3) { create(:variant, product: product, option_values: [option_value3], price: '89.99', sku: 'VAR-3') }
|
272
|
+
|
273
|
+
before do
|
274
|
+
product.option_types << option_type
|
275
|
+
product.variants << [variant1, variant2, variant3]
|
276
|
+
product.tap(&:save)
|
277
|
+
product.stock_items.last.update count_on_hand: 0, backorderable: false
|
278
|
+
end
|
279
|
+
|
280
|
+
context 'Make sure the requested variant' do
|
281
|
+
it 'shows the correct price in the HTML' do
|
282
|
+
visit spree.product_path(product) + '?variant=' + variant3.id.to_s
|
283
|
+
expect(page).to have_content(variant3.price.to_s)
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'shows back-ordered in the HTML when product is on backorder' do
|
287
|
+
visit spree.product_path(product) + '?variant=' + variant2.id.to_s
|
288
|
+
expect(page).to have_content('BACKORDERED')
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'shows out of stock in the HTML when the product is unavailable' do
|
292
|
+
visit spree.product_path(product) + '?variant=' + variant3.id.to_s
|
293
|
+
expect(page).to have_content('OUT OF STOCK')
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'does not update the variant HTML details if no variant is matched' do
|
297
|
+
visit spree.product_path(product) + '?variant=9283923297832973283'
|
298
|
+
expect(page).to have_content(variant1.price.to_s)
|
299
|
+
expect(page).to have_content('BACKORDERED')
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'sets JSON in the Schema.org SKU, URL, price and availability' do
|
303
|
+
visit spree.product_path(product) + '?variant=' + variant3.id.to_s
|
304
|
+
|
305
|
+
jsonld = page.find('script[type="application/ld+json"]', visible: false).text(:all)
|
306
|
+
jsonstring = Capybara.string(jsonld)
|
307
|
+
|
308
|
+
expect(jsonstring).to have_text('?variant=' + variant3.id.to_s)
|
309
|
+
expect(jsonstring).to have_text('"availability":"OutOfStock"')
|
310
|
+
expect(jsonstring).to have_text('"sku":"VAR-3"')
|
311
|
+
expect(jsonstring).to have_text('"price":"89.99"')
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SpreeAnalyticsTrackers::Segment::OrderPresenter, type: :presenter do
|
4
|
+
let(:order) { create(:order_with_line_items) }
|
5
|
+
|
6
|
+
subject { described_class.new(order) }
|
7
|
+
|
8
|
+
describe '#to_json' do
|
9
|
+
let(:result) do
|
10
|
+
{
|
11
|
+
order_id: order.number.to_s,
|
12
|
+
total: order.total&.to_f,
|
13
|
+
shipping: order.shipment_total&.to_f,
|
14
|
+
tax: order.additional_tax_total&.to_f,
|
15
|
+
discount: order.promo_total&.to_f,
|
16
|
+
coupon: order.promo_code,
|
17
|
+
currency: order.currency,
|
18
|
+
products: order.line_items.map{ |line_item|
|
19
|
+
{
|
20
|
+
product_id: line_item.product_id&.to_s,
|
21
|
+
sku: line_item.sku&.to_s,
|
22
|
+
name: line_item.name,
|
23
|
+
price: line_item.price&.to_f,
|
24
|
+
quantity: line_item.quantity
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns serialized object' do
|
31
|
+
expect(subject.to_json).to eq(result)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|