dugway 1.2.0 → 1.3.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/README.md +32 -7
  4. data/dugway.gemspec +11 -9
  5. data/lib/dugway/cli/server.rb +67 -7
  6. data/lib/dugway/liquid/drops/artists_drop.rb +12 -0
  7. data/lib/dugway/liquid/drops/base_drop.rb +27 -2
  8. data/lib/dugway/liquid/drops/categories_drop.rb +12 -0
  9. data/lib/dugway/liquid/drops/pages_drop.rb +30 -2
  10. data/lib/dugway/liquid/drops/product_drop.rb +11 -0
  11. data/lib/dugway/liquid/drops/products_drop.rb +39 -0
  12. data/lib/dugway/liquid/drops/theme_drop.rb +29 -6
  13. data/lib/dugway/liquid/filters/core_filters.rb +169 -7
  14. data/lib/dugway/liquid/filters/font_filters.rb +1 -0
  15. data/lib/dugway/liquid/filters/util_filters.rb +1 -10
  16. data/lib/dugway/liquid/tags/get.rb +6 -6
  17. data/lib/dugway/liquid/tags/paginate.rb +61 -11
  18. data/lib/dugway/store.rb +39 -1
  19. data/lib/dugway/theme.rb +72 -33
  20. data/lib/dugway/version.rb +1 -1
  21. data/lib/dugway.rb +24 -1
  22. data/locales/storefront.de.yml +2 -0
  23. data/locales/storefront.en-CA.yml +2 -0
  24. data/locales/storefront.en-GB.yml +2 -0
  25. data/locales/storefront.en-US.yml +2 -0
  26. data/locales/storefront.es-ES.yml +2 -0
  27. data/locales/storefront.es-MX.yml +2 -0
  28. data/locales/storefront.fr-CA.yml +2 -0
  29. data/locales/storefront.fr-FR.yml +2 -0
  30. data/locales/storefront.id.yml +2 -0
  31. data/locales/storefront.it.yml +2 -0
  32. data/locales/storefront.ja.yml +2 -0
  33. data/locales/storefront.ko.yml +2 -0
  34. data/locales/storefront.nl.yml +2 -0
  35. data/locales/storefront.pl.yml +2 -0
  36. data/locales/storefront.pt-BR.yml +2 -0
  37. data/locales/storefront.pt-PT.yml +2 -0
  38. data/locales/storefront.ro.yml +2 -0
  39. data/locales/storefront.sv.yml +2 -0
  40. data/locales/storefront.tr.yml +2 -0
  41. data/locales/storefront.zh-CN.yml +2 -0
  42. data/locales/storefront.zh-TW.yml +2 -0
  43. data/mise.toml +2 -0
  44. data/spec/spec_helper.rb +3 -0
  45. data/spec/units/dugway/liquid/drops/pages_drop_spec.rb +186 -7
  46. data/spec/units/dugway/liquid/drops/product_drop_spec.rb +17 -0
  47. data/spec/units/dugway/liquid/filters/core_filters_spec.rb +301 -3
  48. data/spec/units/dugway/store_spec.rb +18 -0
  49. data/spec/units/dugway/theme_spec.rb +246 -0
  50. metadata +54 -25
@@ -1,6 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Dugway::Drops::PagesDrop do
4
+ before(:each) do
5
+ # Reset store state before each test
6
+ Dugway.store.instance_variable_set(:@pages, nil)
7
+ Dugway.store.instance_variable_set(:@external_pages, nil)
8
+ end
9
+
4
10
  let(:pages) do
5
11
  Dugway::Drops::PagesDrop.new(
6
12
  Dugway.store.pages.map do |p|
@@ -14,15 +20,58 @@ describe Dugway::Drops::PagesDrop do
14
20
  )
15
21
  end
16
22
 
23
+ def create_pages_with_external(external_pages_data)
24
+ # Mock the store before creating pages
25
+ allow(Dugway.store).to receive(:external_pages).and_return(external_pages_data)
26
+ # Clear memoized pages
27
+ Dugway.store.instance_variable_set(:@pages, nil)
28
+
29
+ Dugway::Drops::PagesDrop.new(
30
+ Dugway.store.pages.map do |p|
31
+ case p["permalink"]
32
+ when "cart"
33
+ Dugway::Drops::CartDrop.new(p)
34
+ else
35
+ Dugway::Drops::PageDrop.new(p)
36
+ end
37
+ end
38
+ )
39
+ end
40
+
17
41
  describe "#all" do
18
- it "should return an array of all pages" do
19
- all = pages.all
20
- all.should be_an_instance_of(Array)
21
- all.size.should == 1
42
+ context "with only custom pages" do
43
+ it "should return an array of all custom pages" do
44
+ all = pages.all
45
+ all.should be_an_instance_of(Array)
46
+ all.size.should == 1
22
47
 
23
- page = all.first
24
- page.should be_an_instance_of(Dugway::Drops::PageDrop)
25
- page.name.should == 'About Us'
48
+ page = all.first
49
+ page.should be_an_instance_of(Dugway::Drops::PageDrop)
50
+ page.name.should == 'About Us'
51
+ end
52
+ end
53
+
54
+ context "with custom and external pages" do
55
+ it "should return an array of all pages (custom + external)" do
56
+ pages_with_external = create_pages_with_external([
57
+ {
58
+ 'name' => 'Subscribe',
59
+ 'permalink' => 'subscribe',
60
+ 'url' => 'https://example.com/subscribe',
61
+ 'category' => 'external'
62
+ }
63
+ ])
64
+
65
+ all = pages_with_external.all
66
+ all.should be_an_instance_of(Array)
67
+ all.size.should == 2
68
+
69
+ custom_page = all.find { |p| p.name == 'About Us' }
70
+ custom_page.should be_an_instance_of(Dugway::Drops::PageDrop)
71
+
72
+ external_page = all.find { |p| p.name == 'Subscribe' }
73
+ external_page.should be_an_instance_of(Dugway::Drops::PageDrop)
74
+ end
26
75
  end
27
76
  end
28
77
 
@@ -46,6 +95,136 @@ describe Dugway::Drops::PagesDrop do
46
95
  end
47
96
  end
48
97
 
98
+ describe "#subscribe_page" do
99
+ context "when subscribe_url is configured" do
100
+ it "should return the subscribe page" do
101
+ allow(Dugway.store).to receive(:subscribe_url).and_return('https://example.com/subscribe')
102
+ pages_with_subscribe = create_pages_with_external([
103
+ {
104
+ 'name' => 'Subscribe',
105
+ 'permalink' => 'subscribe',
106
+ 'url' => 'https://example.com/subscribe',
107
+ 'category' => 'external'
108
+ }
109
+ ])
110
+
111
+ subscribe_page = pages_with_subscribe.subscribe_page
112
+ subscribe_page.should be_an_instance_of(Dugway::Drops::PageDrop)
113
+ subscribe_page.name.should == 'Subscribe'
114
+ subscribe_page.url.should == 'https://example.com/subscribe'
115
+ end
116
+ end
117
+
118
+ context "when subscribe_url is not configured" do
119
+ it "should return nil" do
120
+ allow(Dugway.store).to receive(:subscribe_url).and_return(nil)
121
+ pages_without_subscribe = create_pages_with_external([])
122
+
123
+ pages_without_subscribe.subscribe_page.should be_nil
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "#custom_pages" do
129
+ it "should return an array of custom pages" do
130
+ custom_pages = pages.custom_pages
131
+ custom_pages.should be_an_instance_of(Array)
132
+ custom_pages.size.should == 1
133
+ custom_pages.first.should be_an_instance_of(Dugway::Drops::PageDrop)
134
+ custom_pages.first.name.should == 'About Us'
135
+ end
136
+ end
137
+
138
+ describe "#external_pages" do
139
+ context "when subscribe_url is configured" do
140
+ it "should return an array with the subscribe page" do
141
+ pages_with_subscribe = create_pages_with_external([
142
+ {
143
+ 'name' => 'Subscribe',
144
+ 'permalink' => 'subscribe',
145
+ 'url' => 'https://example.com/subscribe',
146
+ 'category' => 'external'
147
+ }
148
+ ])
149
+
150
+ external_pages = pages_with_subscribe.external_pages
151
+ external_pages.should be_an_instance_of(Array)
152
+ external_pages.size.should == 1
153
+ external_pages.first.should be_an_instance_of(Dugway::Drops::PageDrop)
154
+ external_pages.first.name.should == 'Subscribe'
155
+ end
156
+ end
157
+
158
+ context "when subscribe_url is not configured" do
159
+ it "should return an empty array" do
160
+ pages_without_subscribe = create_pages_with_external([])
161
+
162
+ pages_without_subscribe.external_pages.should == []
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "#required_pages" do
168
+ def create_pages_with_required(required_pages_data)
169
+ # Mock the store before creating pages
170
+ allow(Dugway.store).to receive(:required_pages).and_return(required_pages_data)
171
+ # Clear memoized pages
172
+ Dugway.store.instance_variable_set(:@pages, nil)
173
+
174
+ Dugway::Drops::PagesDrop.new(
175
+ Dugway.store.pages.map do |p|
176
+ case p["permalink"]
177
+ when "cart"
178
+ Dugway::Drops::CartDrop.new(p)
179
+ else
180
+ Dugway::Drops::PageDrop.new(p)
181
+ end
182
+ end
183
+ )
184
+ end
185
+
186
+ context "when required pages are configured" do
187
+ it "should return an array with the required pages" do
188
+ pages_with_required = create_pages_with_required([
189
+ {
190
+ 'name' => 'Terms of Service',
191
+ 'permalink' => 'terms-of-service',
192
+ 'url' => 'https://example.com/terms',
193
+ 'category' => 'custom',
194
+ 'required' => true
195
+ },
196
+ {
197
+ 'name' => 'Privacy Policy',
198
+ 'permalink' => 'privacy-policy',
199
+ 'url' => 'https://example.com/privacy',
200
+ 'category' => 'custom',
201
+ 'required' => true
202
+ }
203
+ ])
204
+
205
+ required_pages = pages_with_required.required_pages
206
+ required_pages.should be_an_instance_of(Array)
207
+ required_pages.size.should == 2
208
+
209
+ terms_page = required_pages.find { |p| p.name == 'Terms of Service' }
210
+ terms_page.should be_an_instance_of(Dugway::Drops::PageDrop)
211
+ terms_page.url.should == 'https://example.com/terms'
212
+
213
+ privacy_page = required_pages.find { |p| p.name == 'Privacy Policy' }
214
+ privacy_page.should be_an_instance_of(Dugway::Drops::PageDrop)
215
+ privacy_page.url.should == 'https://example.com/privacy'
216
+ end
217
+ end
218
+
219
+ context "when no required pages are configured" do
220
+ it "should return an empty array" do
221
+ pages_without_required = create_pages_with_required([])
222
+
223
+ pages_without_required.required_pages.should == []
224
+ end
225
+ end
226
+ end
227
+
49
228
  private
50
229
 
51
230
  def rendered_template(template, assigns={}, registers={})
@@ -302,4 +302,21 @@ describe Dugway::Drops::ProductDrop do
302
302
  expect(related_products).to eq(related_products_mock)
303
303
  end
304
304
  end
305
+
306
+ describe "#price_suffix" do
307
+ it "should return nil when no price_suffix is configured" do
308
+ allow(Dugway.store).to receive(:price_suffix).and_return(nil)
309
+ product.price_suffix.should be_nil
310
+ end
311
+
312
+ it "should return the price_suffix from store configuration" do
313
+ allow(Dugway.store).to receive(:price_suffix).and_return(' USD')
314
+ product.price_suffix.should == ' USD'
315
+ end
316
+
317
+ it "should return the price_suffix from store options" do
318
+ allow(Dugway.store).to receive(:price_suffix).and_return('+ HI')
319
+ product.price_suffix.should == '+ HI'
320
+ end
321
+ end
305
322
  end
@@ -34,12 +34,251 @@ describe Dugway::Filters::CoreFilters do
34
34
  end
35
35
 
36
36
  it "should support the 'code' format argument" do
37
- rendered_template("{{ 1234.56 | money: 'code' }}").should == '1,234.56 <span class="currency_code">USD</span>'
37
+ rendered_template("{{ 1234.56 | money: 'code' }}").should == '<span class="currency_code">USD</span>1,234.56'
38
38
  end
39
39
 
40
40
  it "should support the 'sign_and_code' format argument" do
41
41
  rendered_template("{{ 1234.56 | money: 'sign_and_code' }}").should == '<span class="currency_sign">$</span>1,234.56 <span class="currency_code">USD</span>'
42
42
  end
43
+
44
+ it "should use theme.money_format when explicitly passed as parameter" do
45
+ theme_drop = Dugway::Drops::ThemeDrop.new({ 'money_format' => 'sign' }, {})
46
+ rendered_template("{{ 1234.56 | money: theme.money_format }}", { 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>1,234.56'
47
+ end
48
+
49
+ it "should use theme.money_format = 'code' when explicitly passed as parameter" do
50
+ theme_drop = Dugway::Drops::ThemeDrop.new({ 'money_format' => 'code' }, {})
51
+ rendered_template("{{ 1234.56 | money: theme.money_format }}", { 'theme' => theme_drop }).should == '<span class="currency_code">USD</span>1,234.56'
52
+ end
53
+
54
+ it "should use theme.money_format = 'sign_and_code' when explicitly passed as parameter" do
55
+ theme_drop = Dugway::Drops::ThemeDrop.new({ 'money_format' => 'sign_and_code' }, {})
56
+ rendered_template("{{ 1234.56 | money: theme.money_format }}", { 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>1,234.56 <span class="currency_code">USD</span>'
57
+ end
58
+
59
+ it "should use default format when no format argument is provided" do
60
+ theme_drop = Dugway::Drops::ThemeDrop.new({ 'money_format' => 'sign' }, {})
61
+ rendered_template("{{ 1234.56 | money }}", { 'theme' => theme_drop }).should == '1,234.56'
62
+ end
63
+
64
+ it "should fall back to default format when theme.money_format is not set" do
65
+ rendered_template("{{ 1234.56 | money }}").should == '1,234.56'
66
+ end
67
+
68
+ it "should handle money_format from dugway.json customization when explicitly passed" do
69
+ # Simulate how the theme would be loaded with dugway.json customization
70
+ customization = { 'money_format' => 'sign' }
71
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
72
+ rendered_template("{{ 1234.56 | money: theme.money_format }}", { 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>1,234.56'
73
+ end
74
+
75
+ it "should handle money: theme.money_format syntax like storefront" do
76
+ # Test the exact syntax the user is using: {{ price | money: theme.money_format }}
77
+ customization = { 'money_format' => 'sign' }
78
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
79
+ rendered_template("{{ 1234.56 | money: theme.money_format }}", { 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>1,234.56'
80
+ end
81
+
82
+ it "should handle money_format 'none' correctly returning just the number" do
83
+ customization = { 'money_format' => 'none' }
84
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
85
+ rendered_template("{{ 1234.56 | money: theme.money_format }}", { 'theme' => theme_drop }).should == '1,234.56'
86
+ end
87
+
88
+ it "should support the 'rounded' format argument" do
89
+ rendered_template("{{ 1234.56 | money: 'rounded' }}").should == '1,235'
90
+ end
91
+
92
+ it "should support the 'sign_rounded' format argument" do
93
+ rendered_template("{{ 1234.56 | money: 'sign_rounded' }}").should == '<span class="currency_sign">$</span>1,235'
94
+ end
95
+
96
+ it "should support the 'code_rounded' format argument" do
97
+ rendered_template("{{ 1234.56 | money: 'code_rounded' }}").should == '<span class="currency_code">USD</span>1,235'
98
+ end
99
+
100
+ it "should round up 10.99 to 11 with rounded format" do
101
+ rendered_template("{{ 10.99 | money: 'rounded' }}").should == '11'
102
+ end
103
+
104
+ it "should round up 10.99 to $11 with sign_rounded format" do
105
+ rendered_template("{{ 10.99 | money: 'sign_rounded' }}").should == '<span class="currency_sign">$</span>11'
106
+ end
107
+
108
+ it "should round up 10.99 to 11 USD with code_rounded format" do
109
+ rendered_template("{{ 10.99 | money: 'code_rounded' }}").should == '<span class="currency_code">USD</span>11'
110
+ end
111
+
112
+ it "should handle exact amounts with rounded formats" do
113
+ rendered_template("{{ 10.00 | money: 'rounded' }}").should == '10'
114
+ rendered_template("{{ 10.00 | money: 'sign_rounded' }}").should == '<span class="currency_sign">$</span>10'
115
+ rendered_template("{{ 10.00 | money: 'code_rounded' }}").should == '<span class="currency_code">USD</span>10'
116
+ end
117
+ end
118
+
119
+ describe "#product_price" do
120
+ it "should handle fixed pricing products" do
121
+ product = { 'default_price' => 1234.56, 'variable_pricing' => false }
122
+ rendered_template("{{ product | product_price }}", { 'product' => product }).should == '1,234.56'
123
+ end
124
+
125
+ it "should handle fixed pricing products with format" do
126
+ product = { 'default_price' => 1234.56, 'variable_pricing' => false }
127
+ rendered_template("{{ product | product_price: 'sign' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>1,234.56'
128
+ end
129
+
130
+ it "should handle variable pricing products with default format" do
131
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
132
+ rendered_template("{{ product | product_price }}", { 'product' => product }).should == '10.00 - 20.00'
133
+ end
134
+
135
+ it "should handle variable pricing products with sign format" do
136
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
137
+ rendered_template("{{ product | product_price: 'sign' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10.00 - <span class="currency_sign">$</span>20.00'
138
+ end
139
+
140
+ it "should handle variable pricing products with code format" do
141
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
142
+ rendered_template("{{ product | product_price: 'code' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>10.00 - 20.00'
143
+ end
144
+
145
+ it "should return empty for nil product" do
146
+ rendered_template("{{ nil | product_price }}").should == ''
147
+ end
148
+
149
+ it "should fallback to price field if default_price is not present" do
150
+ product = { 'price' => 1234.56, 'variable_pricing' => false }
151
+ rendered_template("{{ product | product_price }}", { 'product' => product }).should == '1,234.56'
152
+ end
153
+
154
+ it "should work with theme.money_format for fixed pricing" do
155
+ product = { 'default_price' => 1234.56, 'variable_pricing' => false }
156
+ customization = { 'money_format' => 'sign' }
157
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
158
+ rendered_template("{{ product | product_price: theme.money_format }}", { 'product' => product, 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>1,234.56'
159
+ end
160
+
161
+ it "should work with theme.money_format for variable pricing" do
162
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
163
+ customization = { 'money_format' => 'sign' }
164
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
165
+ rendered_template("{{ product | product_price: theme.money_format }}", { 'product' => product, 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>10.00 - <span class="currency_sign">$</span>20.00'
166
+ end
167
+
168
+ it "should handle locale-specific currency formatting" do
169
+ # Test that the currency code position respects locale formatting
170
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
171
+ rendered_template("{{ product | product_price: 'code' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>10.00 - 20.00'
172
+ end
173
+
174
+ # Tests for range_format parameter
175
+ it "should show min price only when range_format is 'min_only'" do
176
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
177
+ rendered_template("{{ product | product_price: 'sign', 'min_only' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10.00'
178
+ end
179
+
180
+ it "should show min price only with code format when range_format is 'min_only'" do
181
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
182
+ rendered_template("{{ product | product_price: 'code', 'min_only' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>10.00'
183
+ end
184
+
185
+ it "should show max price only when range_format is 'max_only'" do
186
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
187
+ rendered_template("{{ product | product_price: 'sign', 'max_only' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>20.00'
188
+ end
189
+
190
+ it "should show max price only with code format when range_format is 'max_only'" do
191
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
192
+ rendered_template("{{ product | product_price: 'code', 'max_only' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>20.00'
193
+ end
194
+
195
+ it "should show range when range_format is 'default'" do
196
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
197
+ rendered_template("{{ product | product_price: 'sign', 'default' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10.00 - <span class="currency_sign">$</span>20.00'
198
+ end
199
+
200
+ it "should show range when range_format is nil" do
201
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
202
+ rendered_template("{{ product | product_price: 'sign', nil }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10.00 - <span class="currency_sign">$</span>20.00'
203
+ end
204
+
205
+ it "should show range when range_format is an invalid value" do
206
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
207
+ rendered_template("{{ product | product_price: 'sign', 'invalid' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10.00 - <span class="currency_sign">$</span>20.00'
208
+ end
209
+
210
+ it "should show range by default when range_format is not specified" do
211
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
212
+ rendered_template("{{ product | product_price: 'sign' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10.00 - <span class="currency_sign">$</span>20.00'
213
+ end
214
+
215
+ # Test ThemeDrop integration for real-world usage
216
+ it "should work with theme-based range format when 'min_only'" do
217
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
218
+ customization = { 'price_range_format' => 'min_only' }
219
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
220
+ rendered_template("{{ product | product_price: 'sign', theme.price_range_format }}", { 'product' => product, 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>10.00'
221
+ end
222
+
223
+ it "should work with theme-based range format when 'max_only'" do
224
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
225
+ customization = { 'price_range_format' => 'max_only' }
226
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
227
+ rendered_template("{{ product | product_price: 'sign', theme.price_range_format }}", { 'product' => product, 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>20.00'
228
+ end
229
+
230
+ it "should work with both theme.money_format and theme-based range format" do
231
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
232
+ customization = { 'money_format' => 'sign', 'price_range_format' => 'min_only' }
233
+ theme_drop = Dugway::Drops::ThemeDrop.new(customization, {})
234
+ rendered_template("{{ product | product_price: theme.money_format, theme.price_range_format }}", { 'product' => product, 'theme' => theme_drop }).should == '<span class="currency_sign">$</span>10.00'
235
+ end
236
+
237
+ # Tests for rounded formats
238
+ it "should handle fixed pricing products with rounded format" do
239
+ product = { 'default_price' => 1234.56, 'variable_pricing' => false }
240
+ rendered_template("{{ product | product_price: 'rounded' }}", { 'product' => product }).should == '1,235'
241
+ end
242
+
243
+ it "should handle fixed pricing products with sign_rounded format" do
244
+ product = { 'default_price' => 1234.56, 'variable_pricing' => false }
245
+ rendered_template("{{ product | product_price: 'sign_rounded' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>1,235'
246
+ end
247
+
248
+ it "should handle fixed pricing products with code_rounded format" do
249
+ product = { 'default_price' => 1234.56, 'variable_pricing' => false }
250
+ rendered_template("{{ product | product_price: 'code_rounded' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>1,235'
251
+ end
252
+
253
+ it "should handle variable pricing products with rounded format" do
254
+ product = { 'min_price' => 10.99, 'max_price' => 20.99, 'variable_pricing' => true }
255
+ rendered_template("{{ product | product_price: 'rounded' }}", { 'product' => product }).should == '11 - 21'
256
+ end
257
+
258
+ it "should handle variable pricing products with sign_rounded format" do
259
+ product = { 'min_price' => 10.99, 'max_price' => 20.99, 'variable_pricing' => true }
260
+ rendered_template("{{ product | product_price: 'sign_rounded' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>11 - <span class="currency_sign">$</span>21'
261
+ end
262
+
263
+ it "should handle variable pricing products with code_rounded format" do
264
+ product = { 'min_price' => 10.99, 'max_price' => 20.99, 'variable_pricing' => true }
265
+ rendered_template("{{ product | product_price: 'code_rounded' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>11 - 21'
266
+ end
267
+
268
+ it "should handle rounded formats with range_format min_only" do
269
+ product = { 'min_price' => 10.99, 'max_price' => 20.99, 'variable_pricing' => true }
270
+ rendered_template("{{ product | product_price: 'sign_rounded', 'min_only' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>11'
271
+ end
272
+
273
+ it "should handle rounded formats with range_format max_only" do
274
+ product = { 'min_price' => 10.99, 'max_price' => 20.99, 'variable_pricing' => true }
275
+ rendered_template("{{ product | product_price: 'code_rounded', 'max_only' }}", { 'product' => product }).should == '<span class="currency_code">USD</span>21'
276
+ end
277
+
278
+ it "should handle exact amounts with rounded variable pricing" do
279
+ product = { 'min_price' => 10.00, 'max_price' => 20.00, 'variable_pricing' => true }
280
+ rendered_template("{{ product | product_price: 'sign_rounded' }}", { 'product' => product }).should == '<span class="currency_sign">$</span>10 - <span class="currency_sign">$</span>20'
281
+ end
43
282
  end
44
283
 
45
284
  describe "#money_with_sign" do
@@ -50,7 +289,62 @@ describe Dugway::Filters::CoreFilters do
50
289
 
51
290
  describe "#money_with_code" do
52
291
  it "should convert a number to currency format with a code" do
53
- rendered_template("{{ 1234.56 | money_with_code }}").should == '1,234.56 <span class="currency_code">USD</span>'
292
+ rendered_template("{{ 1234.56 | money_with_code }}").should == '<span class="currency_code">USD</span>1,234.56'
293
+ end
294
+
295
+ # Tests for different currencies to ensure locale-specific formatting
296
+ context "with EUR currency" do
297
+ # TODO: The 'eu' locale is invalid and needs to be fixed in the bigcartel-currency-locales gem.
298
+ # Once the gem is updated to use a valid locale like 'de-DE' for EUR, we can update these tests
299
+ # to expect the correct European number formatting (1.200,99 instead of 1,200.99).
300
+ # For now, we're expecting the fallback US formatting due to the invalid locale.
301
+ it "should display currency code after number with space for EUR" do
302
+ eur_currency = { 'sign' => '€', 'name' => 'Euro', 'id' => 4, 'code' => 'EUR', 'locale' => 'eu' }
303
+ # Expected: '1.200,99 <span class="currency_code">EUR</span>' once locale is fixed
304
+ rendered_template_with_currency("{{ 1200.99 | money_with_code }}", eur_currency).should == '1,200.99 <span class="currency_code">EUR</span>'
305
+ end
306
+
307
+ it "should work with money: 'code' format for EUR" do
308
+ eur_currency = { 'sign' => '€', 'name' => 'Euro', 'id' => 4, 'code' => 'EUR', 'locale' => 'eu' }
309
+ # Expected: '1.200,99 <span class="currency_code">EUR</span>' once locale is fixed
310
+ rendered_template_with_currency("{{ 1200.99 | money: 'code' }}", eur_currency).should == '1,200.99 <span class="currency_code">EUR</span>'
311
+ end
312
+ end
313
+
314
+ context "with CHF currency" do
315
+ it "should display currency code before number with space for CHF" do
316
+ chf_currency = { 'sign' => 'CHF', 'name' => 'Swiss Franc', 'id' => 10, 'code' => 'CHF', 'locale' => 'gsw-CH' }
317
+ rendered_template_with_currency("{{ 1200.99 | money_with_code }}", chf_currency).should == '<span class="currency_code">CHF</span> 1\'200.99'
318
+ end
319
+
320
+ it "should work with money: 'code' format for CHF" do
321
+ chf_currency = { 'sign' => 'CHF', 'name' => 'Swiss Franc', 'id' => 10, 'code' => 'CHF', 'locale' => 'gsw-CH' }
322
+ rendered_template_with_currency("{{ 1200.99 | money: 'code' }}", chf_currency).should == '<span class="currency_code">CHF</span> 1\'200.99'
323
+ end
324
+ end
325
+
326
+ context "with JPY currency" do
327
+ it "should display currency code before number with no decimals for JPY" do
328
+ jpy_currency = { 'sign' => '¥', 'name' => 'Japanese Yen', 'id' => 6, 'code' => 'JPY', 'locale' => 'ja' }
329
+ rendered_template_with_currency("{{ 1200.99 | money_with_code }}", jpy_currency).should == '<span class="currency_code">JPY</span>1,201'
330
+ end
331
+
332
+ it "should work with money: 'code' format for JPY" do
333
+ jpy_currency = { 'sign' => '¥', 'name' => 'Japanese Yen', 'id' => 6, 'code' => 'JPY', 'locale' => 'ja' }
334
+ rendered_template_with_currency("{{ 1200.99 | money: 'code' }}", jpy_currency).should == '<span class="currency_code">JPY</span>1,201'
335
+ end
336
+ end
337
+
338
+ context "with GBP currency" do
339
+ it "should display currency code before number with no space for GBP" do
340
+ gbp_currency = { 'sign' => '£', 'name' => 'Pound Sterling', 'id' => 5, 'code' => 'GBP', 'locale' => 'en-GB' }
341
+ rendered_template_with_currency("{{ 1200.99 | money_with_code }}", gbp_currency).should == '<span class="currency_code">GBP</span>1,200.99'
342
+ end
343
+
344
+ it "should work with money: 'code' format for GBP" do
345
+ gbp_currency = { 'sign' => '£', 'name' => 'Pound Sterling', 'id' => 5, 'code' => 'GBP', 'locale' => 'en-GB' }
346
+ rendered_template_with_currency("{{ 1200.99 | money: 'code' }}", gbp_currency).should == '<span class="currency_code">GBP</span>1,200.99'
347
+ end
54
348
  end
55
349
  end
56
350
 
@@ -63,6 +357,10 @@ describe Dugway::Filters::CoreFilters do
63
357
  private
64
358
 
65
359
  def rendered_template(template, assigns={})
66
- Liquid::Template.parse(template).render(assigns, :registers => { :currency => Dugway.store.currency })
360
+ Liquid::Template.parse(template).render(assigns, :registers => { :currency => Dugway.store.currency, :settings => {} })
361
+ end
362
+
363
+ def rendered_template_with_currency(template, currency, assigns={})
364
+ Liquid::Template.parse(template).render(assigns, :registers => { :currency => currency, :settings => {} })
67
365
  end
68
366
  end
@@ -226,4 +226,22 @@ describe Dugway::Store do
226
226
  end
227
227
  end
228
228
  end
229
+
230
+ describe "#price_suffix" do
231
+ context "when price_suffix is in store_options" do
232
+ let(:store) { Dugway::Store.new('dugway', { price_suffix: '+ HI' }) }
233
+
234
+ it "returns the price_suffix from store_options" do
235
+ store.price_suffix.should == '+ HI'
236
+ end
237
+ end
238
+
239
+ context "when price_suffix is not in store_options" do
240
+ let(:store) { Dugway::Store.new('dugway', {}) }
241
+
242
+ it "returns nil" do
243
+ store.price_suffix.should be_nil
244
+ end
245
+ end
246
+ end
229
247
  end