dugway 1.1.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.
- checksums.yaml +4 -4
- data/.bundle/config +2 -0
- data/.github/workflows/main.yml +1 -1
- data/.gitignore +1 -0
- data/README.md +32 -7
- data/dugway.gemspec +11 -9
- data/lib/dugway/application.rb +5 -3
- data/lib/dugway/assets/big_cartel_logo.svg +4 -0
- data/lib/dugway/cli/build.rb +7 -1
- data/lib/dugway/cli/server.rb +69 -9
- data/lib/dugway/cli/templates/source/settings.json +8 -0
- data/lib/dugway/cli/validate.rb +9 -2
- data/lib/dugway/controller.rb +5 -1
- data/lib/dugway/liquid/drops/account_drop.rb +4 -0
- data/lib/dugway/liquid/drops/artists_drop.rb +12 -0
- data/lib/dugway/liquid/drops/base_drop.rb +27 -2
- data/lib/dugway/liquid/drops/categories_drop.rb +12 -0
- data/lib/dugway/liquid/drops/features_drop.rb +144 -0
- data/lib/dugway/liquid/drops/pages_drop.rb +30 -2
- data/lib/dugway/liquid/drops/product_drop.rb +11 -0
- data/lib/dugway/liquid/drops/products_drop.rb +39 -0
- data/lib/dugway/liquid/drops/theme_drop.rb +52 -6
- data/lib/dugway/liquid/drops/translations_drop.rb +122 -0
- data/lib/dugway/liquid/filters/core_filters.rb +169 -7
- data/lib/dugway/liquid/filters/font_filters.rb +1 -0
- data/lib/dugway/liquid/filters/util_filters.rb +1 -10
- data/lib/dugway/liquid/tags/get.rb +6 -6
- data/lib/dugway/liquid/tags/paginate.rb +61 -11
- data/lib/dugway/liquifier.rb +44 -8
- data/lib/dugway/store.rb +46 -3
- data/lib/dugway/theme.rb +151 -15
- data/lib/dugway/version.rb +1 -1
- data/lib/dugway.rb +55 -2
- data/locales/storefront.de.yml +81 -0
- data/locales/storefront.en-CA.yml +81 -0
- data/locales/storefront.en-GB.yml +81 -0
- data/locales/storefront.en-US.yml +81 -0
- data/locales/storefront.es-ES.yml +81 -0
- data/locales/storefront.es-MX.yml +81 -0
- data/locales/storefront.fr-CA.yml +81 -0
- data/locales/storefront.fr-FR.yml +81 -0
- data/locales/storefront.id.yml +81 -0
- data/locales/storefront.it.yml +81 -0
- data/locales/storefront.ja.yml +81 -0
- data/locales/storefront.ko.yml +81 -0
- data/locales/storefront.nl.yml +81 -0
- data/locales/storefront.pl.yml +81 -0
- data/locales/storefront.pt-BR.yml +81 -0
- data/locales/storefront.pt-PT.yml +81 -0
- data/locales/storefront.ro.yml +81 -0
- data/locales/storefront.sv.yml +81 -0
- data/locales/storefront.tr.yml +81 -0
- data/locales/storefront.zh-CN.yml +81 -0
- data/locales/storefront.zh-TW.yml +81 -0
- data/log/dugway.log +1 -0
- data/mise.toml +2 -0
- data/spec/features/page_rendering_spec.rb +4 -4
- data/spec/fixtures/theme/layout.html +2 -0
- data/spec/fixtures/theme/settings.json +6 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/units/dugway/liquid/drops/features_drop_spec.rb +182 -0
- data/spec/units/dugway/liquid/drops/pages_drop_spec.rb +186 -7
- data/spec/units/dugway/liquid/drops/product_drop_spec.rb +17 -0
- data/spec/units/dugway/liquid/drops/theme_drop_spec.rb +45 -0
- data/spec/units/dugway/liquid/drops/translations_drop_spec.rb +292 -0
- data/spec/units/dugway/liquid/filters/core_filters_spec.rb +301 -3
- data/spec/units/dugway/store_spec.rb +55 -0
- data/spec/units/dugway/theme_spec.rb +543 -1
- metadata +84 -25
@@ -189,4 +189,59 @@ describe Dugway::Store do
|
|
189
189
|
store.locale.should == 'en-US'
|
190
190
|
end
|
191
191
|
end
|
192
|
+
|
193
|
+
describe "#website" do
|
194
|
+
context "when website is provided in store_options" do
|
195
|
+
let(:store_options) { { website: 'https://my-custom-override.com' } }
|
196
|
+
let(:store) { Dugway::Store.new('dugway', store_options) }
|
197
|
+
|
198
|
+
it "returns the website from store_options" do
|
199
|
+
store.website.should == 'https://my-custom-override.com'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "when website is not in store_options but in account data" do
|
204
|
+
let(:store) { Dugway::Store.new('dugway', {}) } # No override
|
205
|
+
|
206
|
+
before do
|
207
|
+
# Mock the account data to include a website
|
208
|
+
allow(store).to receive(:account).and_return({ 'website' => 'https://api-provided-site.com' })
|
209
|
+
end
|
210
|
+
|
211
|
+
it "returns the website from account data" do
|
212
|
+
store.website.should == 'https://api-provided-site.com'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context "when website is in neither store_options nor account data" do
|
217
|
+
let(:store) { Dugway::Store.new('dugway', {}) } # No override
|
218
|
+
|
219
|
+
before do
|
220
|
+
# Mock the account data to NOT include a website
|
221
|
+
allow(store).to receive(:account).and_return({})
|
222
|
+
end
|
223
|
+
|
224
|
+
it "returns nil" do
|
225
|
+
store.website.should be_nil
|
226
|
+
end
|
227
|
+
end
|
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
|
192
247
|
end
|
@@ -387,7 +387,7 @@ describe Dugway::Theme do
|
|
387
387
|
{'variable' => 'button_text_color'},
|
388
388
|
{'variable' => 'button_hover_background_color'}
|
389
389
|
],
|
390
|
-
'options' => [{ 'variable' => 'show_search', 'label' => 'Show search' }]
|
390
|
+
'options' => [{ 'variable' => 'show_search', 'section' => 'global_navigation', 'label' => 'Show search' }]
|
391
391
|
} }
|
392
392
|
end
|
393
393
|
|
@@ -417,6 +417,7 @@ describe Dugway::Theme do
|
|
417
417
|
settings = theme.settings.merge({
|
418
418
|
'options' => [{
|
419
419
|
'variable' => 'show_search',
|
420
|
+
'section' => 'global_navigation',
|
420
421
|
'label' => 'Show search',
|
421
422
|
'description' => 'Show search in header'
|
422
423
|
}]
|
@@ -452,7 +453,548 @@ describe Dugway::Theme do
|
|
452
453
|
theme.valid?.should be(false)
|
453
454
|
theme.errors.first.should match(/Missing required color settings:/)
|
454
455
|
end
|
456
|
+
|
457
|
+
it "validates feature flag format and values" do
|
458
|
+
settings = theme.settings.merge({
|
459
|
+
'options' => [{
|
460
|
+
'variable' => 'show_foo',
|
461
|
+
'section' => 'general',
|
462
|
+
'description' => 'Show foo thing',
|
463
|
+
'requires' => [
|
464
|
+
'feature:', # invalid: empty
|
465
|
+
'feature:foo_flag gt visible', # invalid operator
|
466
|
+
'feature:foo_flag eq invalid_value', # invalid value
|
467
|
+
'feature:foo_flag neq visible', # valid
|
468
|
+
'feature:foo_flag eq visible' # valid
|
469
|
+
]
|
470
|
+
}]
|
471
|
+
})
|
472
|
+
allow(theme).to receive(:settings).and_return(settings)
|
473
|
+
|
474
|
+
theme.valid?.should be(false)
|
475
|
+
theme.errors.should include("Option 'show_foo' has invalid feature flag format")
|
476
|
+
theme.errors.should include("Option 'show_foo' has invalid operator 'gt'. Feature flags can only use `eq` or `neq`.")
|
477
|
+
theme.errors.should include("Option 'show_foo' has invalid comparison value 'invalid_value'. Feature flags can only check for `visible`.")
|
478
|
+
|
479
|
+
end
|
480
|
+
|
481
|
+
it "allows options that require image settings" do
|
482
|
+
settings = theme.settings.merge({
|
483
|
+
'options' => [{
|
484
|
+
'variable' => 'show_header_text',
|
485
|
+
'section' => 'general',
|
486
|
+
'description' => 'Show header text when logo is not present',
|
487
|
+
'requires' => 'logo'
|
488
|
+
}],
|
489
|
+
'images' => [{
|
490
|
+
'variable' => 'logo',
|
491
|
+
'section' => 'general',
|
492
|
+
'description' => 'Site logo'
|
493
|
+
}]
|
494
|
+
})
|
495
|
+
allow(theme).to receive(:settings).and_return(settings)
|
496
|
+
|
497
|
+
theme.valid?.should be(true)
|
498
|
+
theme.errors.should be_empty
|
499
|
+
end
|
500
|
+
|
501
|
+
it "allows options that require image_sets settings" do
|
502
|
+
settings = theme.settings.merge({
|
503
|
+
'options' => [{
|
504
|
+
'variable' => 'show_gallery',
|
505
|
+
'section' => 'homepage',
|
506
|
+
'description' => 'Show image gallery',
|
507
|
+
'requires' => 'gallery_images'
|
508
|
+
}],
|
509
|
+
'image_sets' => [{
|
510
|
+
'variable' => 'gallery_images',
|
511
|
+
'section' => 'homepage',
|
512
|
+
'description' => 'Gallery images'
|
513
|
+
}]
|
514
|
+
})
|
515
|
+
allow(theme).to receive(:settings).and_return(settings)
|
516
|
+
|
517
|
+
theme.valid?.should be(true)
|
518
|
+
theme.errors.should be_empty
|
519
|
+
end
|
520
|
+
|
521
|
+
it "fails when requires references unknown image setting" do
|
522
|
+
settings = theme.settings.merge({
|
523
|
+
'options' => [{
|
524
|
+
'variable' => 'show_banner',
|
525
|
+
'section' => 'general',
|
526
|
+
'description' => 'Show banner image',
|
527
|
+
'requires' => 'unknown_image'
|
528
|
+
}]
|
529
|
+
})
|
530
|
+
allow(theme).to receive(:settings).and_return(settings)
|
531
|
+
|
532
|
+
theme.valid?.should be(false)
|
533
|
+
theme.errors.should include("Option 'show_banner' requires unknown setting 'unknown_image'")
|
534
|
+
end
|
535
|
+
|
536
|
+
describe "when validating object-based requires" do
|
537
|
+
let(:base_settings) do
|
538
|
+
{
|
539
|
+
'name' => 'Test Theme',
|
540
|
+
'version' => '1.2.3',
|
541
|
+
'colors' => required_colors.map { |color| {'variable' => color} },
|
542
|
+
'options' => [],
|
543
|
+
'images' => [{'variable' => 'slideshow_image', 'section' => 'homepage'}],
|
544
|
+
'image_sets' => [{'variable' => 'hero_images', 'section' => 'homepage'}]
|
545
|
+
}
|
546
|
+
end
|
547
|
+
|
548
|
+
before(:each) do
|
549
|
+
allow(theme).to receive(:validate_required_color_settings) { true }
|
550
|
+
end
|
551
|
+
|
552
|
+
it "validates 'any' condition with valid settings" do
|
553
|
+
settings = base_settings.merge({
|
554
|
+
'options' => [{
|
555
|
+
'variable' => 'show_hero',
|
556
|
+
'section' => 'homepage',
|
557
|
+
'description' => 'Show hero section',
|
558
|
+
'requires' => [
|
559
|
+
{
|
560
|
+
'any' => [
|
561
|
+
'slideshow_image present',
|
562
|
+
'hero_images present'
|
563
|
+
]
|
564
|
+
}
|
565
|
+
]
|
566
|
+
}]
|
567
|
+
})
|
568
|
+
allow(theme).to receive(:settings).and_return(settings)
|
569
|
+
|
570
|
+
theme.valid?.should be(true)
|
571
|
+
theme.errors.should be_empty
|
572
|
+
end
|
573
|
+
|
574
|
+
it "validates mixed array with both objects and strings" do
|
575
|
+
settings = base_settings.merge({
|
576
|
+
'options' => [{
|
577
|
+
'variable' => 'show_advanced_hero',
|
578
|
+
'section' => 'homepage',
|
579
|
+
'description' => 'Show advanced hero',
|
580
|
+
'requires' => [
|
581
|
+
{
|
582
|
+
'any' => ['slideshow_image present', 'hero_images present']
|
583
|
+
},
|
584
|
+
'inventory'
|
585
|
+
]
|
586
|
+
}]
|
587
|
+
})
|
588
|
+
allow(theme).to receive(:settings).and_return(settings)
|
589
|
+
|
590
|
+
theme.valid?.should be(true)
|
591
|
+
theme.errors.should be_empty
|
592
|
+
end
|
593
|
+
|
594
|
+
it "fails when 'any' condition references unknown settings" do
|
595
|
+
settings = base_settings.merge({
|
596
|
+
'options' => [{
|
597
|
+
'variable' => 'show_gallery',
|
598
|
+
'section' => 'homepage',
|
599
|
+
'description' => 'Show image gallery',
|
600
|
+
'requires' => [
|
601
|
+
{
|
602
|
+
'any' => [
|
603
|
+
'unknown_image present',
|
604
|
+
'slideshow_image present'
|
605
|
+
]
|
606
|
+
}
|
607
|
+
]
|
608
|
+
}]
|
609
|
+
})
|
610
|
+
allow(theme).to receive(:settings).and_return(settings)
|
611
|
+
|
612
|
+
theme.valid?.should be(false)
|
613
|
+
theme.errors.should include("Option 'show_gallery' requires unknown setting 'unknown_image'")
|
614
|
+
end
|
615
|
+
|
616
|
+
it "fails when 'any' condition is not an array" do
|
617
|
+
settings = base_settings.merge({
|
618
|
+
'options' => [{
|
619
|
+
'variable' => 'show_content',
|
620
|
+
'section' => 'homepage',
|
621
|
+
'description' => 'Show content',
|
622
|
+
'requires' => [
|
623
|
+
{
|
624
|
+
'any' => 'slideshow_image present'
|
625
|
+
}
|
626
|
+
]
|
627
|
+
}]
|
628
|
+
})
|
629
|
+
allow(theme).to receive(:settings).and_return(settings)
|
630
|
+
|
631
|
+
theme.valid?.should be(false)
|
632
|
+
theme.errors.should include("Option 'show_content' has invalid 'any' condition - must be an array of rules")
|
633
|
+
end
|
634
|
+
|
635
|
+
it "fails when 'any' condition is empty" do
|
636
|
+
settings = base_settings.merge({
|
637
|
+
'options' => [{
|
638
|
+
'variable' => 'show_empty_any',
|
639
|
+
'section' => 'homepage',
|
640
|
+
'description' => 'Show with empty any',
|
641
|
+
'requires' => [
|
642
|
+
{
|
643
|
+
'any' => []
|
644
|
+
}
|
645
|
+
]
|
646
|
+
}]
|
647
|
+
})
|
648
|
+
allow(theme).to receive(:settings).and_return(settings)
|
649
|
+
|
650
|
+
theme.valid?.should be(false)
|
651
|
+
theme.errors.should include("Option 'show_empty_any' has empty 'any' condition - must contain at least one rule")
|
652
|
+
end
|
653
|
+
|
654
|
+
it "fails when object has unsupported operator" do
|
655
|
+
settings = base_settings.merge({
|
656
|
+
'options' => [{
|
657
|
+
'variable' => 'show_unsupported',
|
658
|
+
'section' => 'homepage',
|
659
|
+
'description' => 'Show with unsupported operator',
|
660
|
+
'requires' => [
|
661
|
+
{
|
662
|
+
'all' => ['slideshow_image present']
|
663
|
+
}
|
664
|
+
]
|
665
|
+
}]
|
666
|
+
})
|
667
|
+
allow(theme).to receive(:settings).and_return(settings)
|
668
|
+
|
669
|
+
theme.valid?.should be(false)
|
670
|
+
theme.errors.should include("Option 'show_unsupported' has unsupported requires object. Only 'any' is currently supported.")
|
671
|
+
end
|
672
|
+
|
673
|
+
it "validates nested operators and values within 'any'" do
|
674
|
+
settings = base_settings.merge({
|
675
|
+
'options' => [
|
676
|
+
{
|
677
|
+
'variable' => 'enable_slideshow',
|
678
|
+
'section' => 'homepage',
|
679
|
+
'description' => 'Enable slideshow',
|
680
|
+
'type' => 'boolean',
|
681
|
+
'default' => true
|
682
|
+
},
|
683
|
+
{
|
684
|
+
'variable' => 'show_conditional_hero',
|
685
|
+
'section' => 'homepage',
|
686
|
+
'description' => 'Show conditional hero',
|
687
|
+
'requires' => [
|
688
|
+
{
|
689
|
+
'any' => [
|
690
|
+
'enable_slideshow eq true',
|
691
|
+
'slideshow_image present'
|
692
|
+
]
|
693
|
+
}
|
694
|
+
]
|
695
|
+
}
|
696
|
+
]
|
697
|
+
})
|
698
|
+
allow(theme).to receive(:settings).and_return(settings)
|
699
|
+
|
700
|
+
theme.valid?.should be(true)
|
701
|
+
theme.errors.should be_empty
|
702
|
+
end
|
703
|
+
|
704
|
+
it "validates feature flags within 'any' condition" do
|
705
|
+
settings = base_settings.merge({
|
706
|
+
'options' => [{
|
707
|
+
'variable' => 'show_feature_dependent',
|
708
|
+
'section' => 'homepage',
|
709
|
+
'description' => 'Show feature dependent content',
|
710
|
+
'requires' => [
|
711
|
+
{
|
712
|
+
'any' => [
|
713
|
+
'feature:advanced_hero eq visible',
|
714
|
+
'slideshow_image present'
|
715
|
+
]
|
716
|
+
}
|
717
|
+
]
|
718
|
+
}]
|
719
|
+
})
|
720
|
+
allow(theme).to receive(:settings).and_return(settings)
|
721
|
+
|
722
|
+
theme.valid?.should be(true)
|
723
|
+
theme.errors.should be_empty
|
724
|
+
end
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
describe "when validating option sections" do
|
729
|
+
let(:valid_sections_string) { Dugway::Theme::VALID_SECTIONS.join(', ') }
|
730
|
+
let(:base_settings) do
|
731
|
+
{
|
732
|
+
'name' => 'Test Theme',
|
733
|
+
'version' => '1.2.3',
|
734
|
+
'colors' => required_colors.map { |color| {'variable' => color} },
|
735
|
+
'options' => []
|
736
|
+
}
|
737
|
+
end
|
738
|
+
|
739
|
+
# Define required_colors here if not already available in this scope
|
740
|
+
let(:required_colors) {[
|
741
|
+
'background_color',
|
742
|
+
'primary_text_color',
|
743
|
+
'link_text_color',
|
744
|
+
'link_hover_color',
|
745
|
+
'button_background_color',
|
746
|
+
'button_text_color',
|
747
|
+
'button_hover_background_color'
|
748
|
+
]}
|
749
|
+
|
750
|
+
# Fix for the failing tests - define constant if needed
|
751
|
+
before(:each) do
|
752
|
+
unless defined?(Dugway::Theme::REQUIRED_FILES)
|
753
|
+
stub_const("Dugway::Theme::REQUIRED_FILES", [
|
754
|
+
"cart.html", "contact.html", "home.html", "layout.html", "maintenance.html",
|
755
|
+
"product.html", "products.html", "screenshot.jpg", "settings.json", "theme.css", "theme.js"
|
756
|
+
])
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
it "passes with a valid section" do
|
761
|
+
settings = base_settings.merge({
|
762
|
+
'options' => [{
|
763
|
+
'variable' => 'valid_option',
|
764
|
+
'description' => 'Valid option',
|
765
|
+
'section' => 'homepage'
|
766
|
+
}]
|
767
|
+
})
|
768
|
+
# Stub settings
|
769
|
+
allow(theme).to receive(:settings).and_return(settings)
|
770
|
+
|
771
|
+
# Stub ALL required files first
|
772
|
+
Dugway::Theme::REQUIRED_FILES.each do |file|
|
773
|
+
allow(theme).to receive(:read_source_file).with(file).and_return("some content")
|
774
|
+
end
|
775
|
+
|
776
|
+
# Then override the specific stub for layout.html
|
777
|
+
allow(theme).to receive(:read_source_file).with('layout.html')
|
778
|
+
.and_return('<body data-bc-page-type="home"><div data-bc-hook="header"></div><div data-bc-hook="footer"></div></body>')
|
779
|
+
|
780
|
+
# Stub color validation
|
781
|
+
allow(theme).to receive(:validate_required_color_settings).and_return(true)
|
782
|
+
|
783
|
+
expect(Kernel).not_to receive(:warn)
|
784
|
+
theme.valid?.should be(true)
|
785
|
+
end
|
786
|
+
|
787
|
+
it "warns appropriately for multiple options with missing or invalid sections" do
|
788
|
+
settings = base_settings.merge({
|
789
|
+
'options' => [
|
790
|
+
{ 'variable' => 'option_1_valid', 'description' => 'Valid', 'section' => 'homepage' },
|
791
|
+
{ 'variable' => 'option_2_missing', 'description' => 'Missing section' }, # Missing section
|
792
|
+
{ 'variable' => 'option_3_invalid', 'description' => 'Invalid section', 'section' => 'bad_section' }, # Invalid section
|
793
|
+
{ 'variable' => 'option_4_valid', 'description' => 'Valid again', 'section' => 'general' },
|
794
|
+
{ 'variable' => 'option_5_missing', 'description' => 'Missing section again' }, # Missing section again
|
795
|
+
{ 'description' => 'Option 6 missing variable and section' }, # Missing variable and section
|
796
|
+
{ 'variable' => 'option_7_invalid', 'description' => 'Invalid section again', 'section' => 'another_bad_one' } # Invalid section again
|
797
|
+
]
|
798
|
+
})
|
799
|
+
# Stub settings
|
800
|
+
allow(theme).to receive(:settings).and_return(settings)
|
801
|
+
|
802
|
+
# Stub ALL required files first
|
803
|
+
Dugway::Theme::REQUIRED_FILES.each do |file|
|
804
|
+
allow(theme).to receive(:read_source_file).with(file).and_return("some content")
|
805
|
+
end
|
806
|
+
|
807
|
+
# Then override the specific stub for layout.html
|
808
|
+
allow(theme).to receive(:read_source_file).with('layout.html')
|
809
|
+
.and_return('<body data-bc-page-type="home"><div data-bc-hook="header"></div><div data-bc-hook="footer"></div></body>')
|
810
|
+
|
811
|
+
# Stub color validation
|
812
|
+
allow(theme).to receive(:validate_required_color_settings).and_return(true)
|
813
|
+
|
814
|
+
# Allow warn to be called so we can spy on it
|
815
|
+
allow(Kernel).to receive(:warn)
|
816
|
+
|
817
|
+
# Call valid? and assert true (since warnings don't invalidate)
|
818
|
+
theme.valid?.should be(true)
|
819
|
+
|
820
|
+
# Verify calls
|
821
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'option_2_missing' is missing the 'section' property.")
|
822
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'option_3_invalid' has an invalid 'section' value: 'bad_section'. Allowed values are: #{valid_sections_string}.")
|
823
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'option_5_missing' is missing the 'section' property.")
|
824
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'unknown' is missing the 'section' property.")
|
825
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'option_7_invalid' has an invalid 'section' value: 'another_bad_one'. Allowed values are: #{valid_sections_string}.")
|
826
|
+
end
|
827
|
+
|
828
|
+
it "handles options without a variable name gracefully when section is invalid" do
|
829
|
+
settings = base_settings.merge({
|
830
|
+
'options' => [{
|
831
|
+
# 'variable' key is missing
|
832
|
+
'description' => 'Option without variable',
|
833
|
+
'section' => 'invalid_area' # Invalid section to trigger warning
|
834
|
+
}]
|
835
|
+
})
|
836
|
+
|
837
|
+
# Stub all necessary methods
|
838
|
+
allow(theme).to receive(:settings).and_return(settings)
|
839
|
+
allow(theme).to receive(:validate_required_color_settings).and_return(true)
|
840
|
+
allow(theme).to receive(:validate_required_layout_attributes).and_return(true)
|
841
|
+
allow(theme).to receive(:validate_options_requires).and_return(true)
|
842
|
+
allow(theme).to receive(:validate_options_descriptions).and_return(true)
|
843
|
+
allow(theme).to receive(:validate_option_defaults).and_return(true)
|
844
|
+
|
845
|
+
# Stub file checks - this is crucial
|
846
|
+
Dugway::Theme::REQUIRED_FILES.each do |file|
|
847
|
+
allow(theme).to receive(:read_source_file).with(file).and_return("some content")
|
848
|
+
end
|
849
|
+
|
850
|
+
# Allow warn to be called
|
851
|
+
allow(Kernel).to receive(:warn)
|
852
|
+
|
853
|
+
# Run the validation
|
854
|
+
theme.valid?.should be(true)
|
855
|
+
|
856
|
+
# Check that the warning was issued
|
857
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'unknown' has an invalid 'section' value: 'invalid_area'. Allowed values are: #{valid_sections_string}.")
|
858
|
+
end
|
859
|
+
|
860
|
+
it "handles options without a variable name gracefully when section is missing" do
|
861
|
+
settings = base_settings.merge({
|
862
|
+
'options' => [{
|
863
|
+
# 'variable' key is missing
|
864
|
+
'description' => 'Option without variable or section'
|
865
|
+
# 'section' key is missing
|
866
|
+
}]
|
867
|
+
})
|
868
|
+
|
869
|
+
# Stub all necessary methods
|
870
|
+
allow(theme).to receive(:settings).and_return(settings)
|
871
|
+
allow(theme).to receive(:validate_required_color_settings).and_return(true)
|
872
|
+
allow(theme).to receive(:validate_required_layout_attributes).and_return(true)
|
873
|
+
allow(theme).to receive(:validate_options_requires).and_return(true)
|
874
|
+
allow(theme).to receive(:validate_options_descriptions).and_return(true)
|
875
|
+
allow(theme).to receive(:validate_option_defaults).and_return(true)
|
876
|
+
|
877
|
+
# Stub file checks
|
878
|
+
Dugway::Theme::REQUIRED_FILES.each do |file|
|
879
|
+
allow(theme).to receive(:read_source_file).with(file).and_return("some content")
|
880
|
+
end
|
881
|
+
|
882
|
+
# Allow warn to be called
|
883
|
+
allow(Kernel).to receive(:warn)
|
884
|
+
|
885
|
+
# Run the validation
|
886
|
+
theme.valid?.should be(true)
|
887
|
+
|
888
|
+
# Check that the warning was issued
|
889
|
+
expect(Kernel).to have_received(:warn).with("Warning: Theme setting 'unknown' is missing the 'section' property.")
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
end
|
894
|
+
|
895
|
+
shared_examples "option value validation" do |value_type|
|
896
|
+
context "when type is 'select' with explicit options" do
|
897
|
+
let(:options) do
|
898
|
+
[{
|
899
|
+
'type' => 'select',
|
900
|
+
'variable' => 'hero_overlay_opacity',
|
901
|
+
'options' => [['Bright', 'bright'], ['Dark', 'dark']],
|
902
|
+
value_type => 'bright'
|
903
|
+
}]
|
904
|
+
end
|
905
|
+
|
906
|
+
it "passes when #{value_type} matches a valid option" do
|
907
|
+
theme.instance_variable_set(:@errors, [])
|
908
|
+
theme.send(:validate_option_defaults)
|
909
|
+
expect(theme.errors).to be_empty
|
910
|
+
end
|
911
|
+
|
912
|
+
it "flags invalid #{value_type} values" do
|
913
|
+
options.first[value_type] = 'invalid'
|
914
|
+
theme.instance_variable_set(:@errors, [])
|
915
|
+
theme.send(:validate_option_defaults)
|
916
|
+
expect(theme.errors).to include("#{value_type.capitalize} 'invalid' is not a valid option for hero_overlay_opacity")
|
917
|
+
end
|
455
918
|
end
|
919
|
+
|
920
|
+
context "when type is 'select' with numeric range" do
|
921
|
+
let(:options) do
|
922
|
+
[{
|
923
|
+
'type' => 'select',
|
924
|
+
'variable' => 'featured_products',
|
925
|
+
'options' => '0..100',
|
926
|
+
value_type => 50
|
927
|
+
}]
|
928
|
+
end
|
929
|
+
|
930
|
+
it "passes when #{value_type} is within range" do
|
931
|
+
theme.instance_variable_set(:@errors, [])
|
932
|
+
theme.send(:validate_option_defaults)
|
933
|
+
expect(theme.errors).to be_empty
|
934
|
+
end
|
935
|
+
|
936
|
+
it "flags #{value_type} outside the range" do
|
937
|
+
options.first[value_type] = 101
|
938
|
+
theme.instance_variable_set(:@errors, [])
|
939
|
+
theme.send(:validate_option_defaults)
|
940
|
+
expect(theme.errors).to include("#{value_type.capitalize} '101' is out of range for featured_products")
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
context "when type is 'boolean'" do
|
945
|
+
let(:options) do
|
946
|
+
[{
|
947
|
+
'type' => 'boolean',
|
948
|
+
'variable' => 'show_home_page_categories',
|
949
|
+
value_type => true
|
950
|
+
}]
|
951
|
+
end
|
952
|
+
|
953
|
+
it "passes when #{value_type} is a valid boolean" do
|
954
|
+
theme.instance_variable_set(:@errors, [])
|
955
|
+
theme.send(:validate_option_defaults)
|
956
|
+
expect(theme.errors).to be_empty
|
957
|
+
end
|
958
|
+
|
959
|
+
it "flags non-boolean #{value_type}" do
|
960
|
+
options.first[value_type] = 'true'
|
961
|
+
theme.instance_variable_set(:@errors, [])
|
962
|
+
theme.send(:validate_option_defaults)
|
963
|
+
expect(theme.errors).to include("#{value_type.capitalize} 'true' is not a boolean for show_home_page_categories")
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
context "when type is 'select' with product_orders" do
|
968
|
+
let(:options) do
|
969
|
+
[{
|
970
|
+
'type' => 'select',
|
971
|
+
'variable' => 'related_products_order',
|
972
|
+
'options' => 'product_orders',
|
973
|
+
value_type => 'top-selling'
|
974
|
+
}]
|
975
|
+
end
|
976
|
+
|
977
|
+
it "passes when #{value_type} matches a valid product_orders value" do
|
978
|
+
theme.instance_variable_set(:@errors, [])
|
979
|
+
theme.send(:validate_option_defaults)
|
980
|
+
expect(theme.errors).to be_empty
|
981
|
+
end
|
982
|
+
|
983
|
+
it "flags invalid #{value_type} values" do
|
984
|
+
options.first[value_type] = 'invalid'
|
985
|
+
theme.instance_variable_set(:@errors, [])
|
986
|
+
theme.send(:validate_option_defaults)
|
987
|
+
expect(theme.errors).to include("#{value_type.capitalize} 'invalid' is not a valid option for related_products_order")
|
988
|
+
end
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
describe "#validate_option_defaults" do
|
993
|
+
let(:settings) { { 'options' => options } }
|
994
|
+
before { allow(theme).to receive(:settings).and_return(settings) }
|
995
|
+
|
996
|
+
it_behaves_like "option value validation", 'default'
|
997
|
+
it_behaves_like "option value validation", 'upgrade_default'
|
456
998
|
end
|
457
999
|
|
458
1000
|
def read_file(file_name)
|