spree_mobility 1.0.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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.hound.yml +21 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +7 -0
  6. data/.travis.yml +45 -0
  7. data/Appraisals +8 -0
  8. data/CONTRIBUTING.md +81 -0
  9. data/Gemfile +8 -0
  10. data/Guardfile +12 -0
  11. data/LICENSE.md +26 -0
  12. data/README.md +92 -0
  13. data/Rakefile +24 -0
  14. data/app/assets/javascripts/spree/backend/spree_mobility.js +2 -0
  15. data/app/assets/javascripts/spree/backend/taxon_tree_menu.js +49 -0
  16. data/app/assets/javascripts/spree/backend/translations.js +50 -0
  17. data/app/controllers/concerns/spree_mobility/controller_mobility_helper.rb +18 -0
  18. data/app/controllers/spree/admin/option_values_controller_decorator.rb +23 -0
  19. data/app/controllers/spree/admin/product_properties_controller_decorator.rb +22 -0
  20. data/app/controllers/spree/admin/shipping_methods_controller_decorator.rb +18 -0
  21. data/app/controllers/spree/admin/translations_controller.rb +53 -0
  22. data/app/controllers/spree/api/base_controller_decorator.rb +5 -0
  23. data/app/controllers/spree/base_controller_decorator.rb +5 -0
  24. data/app/helpers/spree_mobility/locale_helper.rb +11 -0
  25. data/app/models/concerns/spree_mobility/translatable.rb +63 -0
  26. data/app/overrides/spree/admin/option_types/_option_value_fields/add_translation.rb +10 -0
  27. data/app/overrides/spree/admin/option_types/index/add_translation.rb +8 -0
  28. data/app/overrides/spree/admin/product_properties/_product_property_fields/add_translation.rb +10 -0
  29. data/app/overrides/spree/admin/products/index/search_by_name_or_sku.rb +21 -0
  30. data/app/overrides/spree/admin/promotions/index/add_translation_link.rb +8 -0
  31. data/app/overrides/spree/admin/properties/index/add_translation.rb +8 -0
  32. data/app/overrides/spree/admin/shared/_product_tabs/add_translations.rb +10 -0
  33. data/app/overrides/spree/admin/shared/_translations/translation.rb +8 -0
  34. data/app/overrides/spree/admin/shared/sub_menu/_configuration/store_translations.rb +8 -0
  35. data/app/overrides/spree/admin/shipping_methods/index/add_translation.rb +8 -0
  36. data/app/overrides/spree/admin/taxonomies/_list/add_translations.rb +8 -0
  37. data/app/views/spree/admin/translations/_fields.html.erb +12 -0
  38. data/app/views/spree/admin/translations/_form.html.erb +13 -0
  39. data/app/views/spree/admin/translations/_form_fields.html.erb +38 -0
  40. data/app/views/spree/admin/translations/_settings.html.erb +22 -0
  41. data/app/views/spree/admin/translations/option_type.html.erb +9 -0
  42. data/app/views/spree/admin/translations/option_value.html.erb +25 -0
  43. data/app/views/spree/admin/translations/product.html.erb +7 -0
  44. data/app/views/spree/admin/translations/product_property.html.erb +23 -0
  45. data/app/views/spree/admin/translations/promotion.html.erb +9 -0
  46. data/app/views/spree/admin/translations/property.html.erb +9 -0
  47. data/app/views/spree/admin/translations/shipping_method.html.erb +9 -0
  48. data/app/views/spree/admin/translations/store.html.erb +25 -0
  49. data/app/views/spree/admin/translations/taxon.html.erb +25 -0
  50. data/app/views/spree/admin/translations/taxonomy.html.erb +9 -0
  51. data/bin/rails +7 -0
  52. data/config/initializers/enable_extensions.rb +27 -0
  53. data/config/initializers/form_builder_mobility_patch.rb +53 -0
  54. data/config/initializers/mobility.rb +118 -0
  55. data/config/initializers/spree_ransack.rb +31 -0
  56. data/config/locales/be.yml +6 -0
  57. data/config/locales/bg.yml +6 -0
  58. data/config/locales/en.yml +6 -0
  59. data/config/locales/it.yml +6 -0
  60. data/config/locales/ru.yml +6 -0
  61. data/config/locales/zh-TW.yml +6 -0
  62. data/config/routes.rb +7 -0
  63. data/db/migrate/20220411041407_add_translations_to_main_models.rb +83 -0
  64. data/db/migrate/20220412095648_remove_null_constraints_from_spree_tables.rb +13 -0
  65. data/db/migrate/20220413095648_migrate_translation_data.rb +53 -0
  66. data/gemfiles/spree_4_2.gemfile +8 -0
  67. data/gemfiles/spree_master.gemfile +10 -0
  68. data/lib/generators/spree_mobility/install/install_generator.rb +23 -0
  69. data/lib/spree_mobility/configuration.rb +12 -0
  70. data/lib/spree_mobility/core_ext/mobility/backends/active_record/table/mobility_acts_as_paranoid_decorator.rb +31 -0
  71. data/lib/spree_mobility/core_ext/spree/option_type_decorator.rb +11 -0
  72. data/lib/spree_mobility/core_ext/spree/option_value_decorator.rb +30 -0
  73. data/lib/spree_mobility/core_ext/spree/product_decorator.rb +87 -0
  74. data/lib/spree_mobility/core_ext/spree/product_property_decorator.rb +6 -0
  75. data/lib/spree_mobility/core_ext/spree/product_scopes_with_mobility_decorator.rb +16 -0
  76. data/lib/spree_mobility/core_ext/spree/products/find_with_mobility_decorator.rb +12 -0
  77. data/lib/spree_mobility/core_ext/spree/promotion_decorator.rb +11 -0
  78. data/lib/spree_mobility/core_ext/spree/property_decorator.rb +26 -0
  79. data/lib/spree_mobility/core_ext/spree/shipping_method_decorator.rb +10 -0
  80. data/lib/spree_mobility/core_ext/spree/store_decorator.rb +12 -0
  81. data/lib/spree_mobility/core_ext/spree/taxon_decorator.rb +54 -0
  82. data/lib/spree_mobility/core_ext/spree/taxonomy_decorator.rb +29 -0
  83. data/lib/spree_mobility/core_ext/spree/variant_decorator.rb +19 -0
  84. data/lib/spree_mobility/engine.rb +33 -0
  85. data/lib/spree_mobility/fallbacks.rb +45 -0
  86. data/lib/spree_mobility/translation_query.rb +36 -0
  87. data/lib/spree_mobility/version.rb +18 -0
  88. data/lib/spree_mobility.rb +47 -0
  89. data/spec/features/admin/products_spec.rb +15 -0
  90. data/spec/features/admin/translations_spec.rb +259 -0
  91. data/spec/features/translations_spec.rb +35 -0
  92. data/spec/models/product_spec.rb +77 -0
  93. data/spec/models/taxon_spec.rb +16 -0
  94. data/spec/models/translated_models_spec.rb +33 -0
  95. data/spec/models/variant_spec.rb +33 -0
  96. data/spec/spec_helper.rb +10 -0
  97. data/spec/support/i18n.rb +5 -0
  98. data/spec/support/matchers/be_a_thorough_translation_of_matcher.rb +21 -0
  99. data/spec/support/matchers/have_text_like.rb +3 -0
  100. data/spec/support/shared_contexts/translatable_context.rb +63 -0
  101. data/spree_mobility.gemspec +37 -0
  102. metadata +316 -0
@@ -0,0 +1,12 @@
1
+ module SpreeMobility::CoreExt::Spree::StoreDecorator
2
+ def self.prepended(base)
3
+ base.include SpreeMobility::Translatable
4
+ if ::ApplicationRecord.connected? && ::ApplicationRecord.connection.table_exists?(:spree_store_translations)
5
+ SpreeMobility.translates_for base, :name, :meta_description, :meta_keywords, :seo_title
6
+
7
+ base.translation_class.class_eval do
8
+ validates :name, presence: true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ module SpreeMobility::CoreExt::Spree::TaxonDecorator
2
+ module TranslationMethods
3
+ def permalink_uniqueness_validation
4
+ return unless permalink.present?
5
+ return unless translated_model
6
+ check_scope =
7
+ ::Spree::Taxon.
8
+ where.not(id: translated_model.id).
9
+ where(parent_id: translated_model.parent_id,
10
+ taxonomy_id: translated_model.taxonomy_id).
11
+ joins(:translations).
12
+ where(spree_taxon_translations: { locale: locale }).
13
+ where('LOWER(spree_taxon_translations.permalink) = LOWER(?)', permalink)
14
+ if check_scope.exists?
15
+ errors.add(:permalink, :taken, value: permalink)
16
+ end
17
+ end
18
+
19
+ def name_uniqueness_validation
20
+ return unless name.present?
21
+ return unless translated_model
22
+ check_scope =
23
+ ::Spree::Taxon.
24
+ where.not(id: translated_model.id).
25
+ where(parent_id: translated_model.parent_id,
26
+ taxonomy_id: translated_model.taxonomy_id).
27
+ joins(:translations).
28
+ where(spree_taxon_translations: { locale: locale }).
29
+ where('LOWER(spree_taxon_translations.name) = LOWER(?)', name)
30
+ if check_scope.exists?
31
+ errors.add(:name, :taken, value: name)
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.prepended(base)
37
+ base.include SpreeMobility::Translatable
38
+ SpreeMobility.translates_for base, :name, :description, :meta_title,
39
+ :meta_description, :meta_keywords, :permalink
40
+ base.friendly_id :permalink, slug_column: :permalink, use: [:history, :mobility]
41
+
42
+ base.translation_class.class_eval do
43
+ include TranslationMethods
44
+ validates :name, presence: true
45
+ validate :name_uniqueness_validation
46
+ with_options length: { maximum: 255 }, allow_blank: true do
47
+ validates :meta_keywords
48
+ validates :meta_description
49
+ validates :meta_title
50
+ end
51
+ validate :permalink_uniqueness_validation
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module SpreeMobility::CoreExt::Spree::TaxonomyDecorator
2
+ module TranslationMethods
3
+ def name_uniqueness_validation
4
+ return unless name.present?
5
+ return unless translated_model
6
+ check_scope =
7
+ ::Spree::Taxonomy.
8
+ where.not(id: translated_model.id).
9
+ where(store_id: translated_model.store_id).
10
+ joins(:translations).
11
+ where(spree_taxonomy_translations: { locale: locale }).
12
+ where('LOWER(spree_taxonomy_translations.name) = LOWER(?)', name)
13
+ if check_scope.exists?
14
+ errors.add(:name, :taken, value: name)
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.prepended(base)
20
+ base.include SpreeMobility::Translatable
21
+ SpreeMobility.translates_for base, :name
22
+
23
+ base.translation_class.class_eval do
24
+ include TranslationMethods
25
+ validates :name, presence: true
26
+ validate :name_uniqueness_validation
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module SpreeMobility::CoreExt::Spree
2
+ module VariantDecorator
3
+ module ClassMethods
4
+ def product_name_or_sku_cont(query)
5
+ helper =
6
+ SpreeMobility::TranslationQuery.new(
7
+ ::Spree::Product.mobility_backend_class(:name))
8
+
9
+ helper.add_joins(self.joins(:product)).
10
+ where(
11
+ "(LOWER(#{helper.col_name(:name)}) LIKE LOWER(:query)) OR (LOWER(#{::Spree::Variant.table_name}.sku) LIKE LOWER(:query))", query: "%#{query}%").distinct
12
+ end
13
+
14
+ def search_by_product_name_or_sku(query)
15
+ product_name_or_sku_cont(query)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ require 'mobility'
2
+ require 'friendly_id/mobility'
3
+
4
+ module SpreeMobility
5
+ class Engine < Rails::Engine
6
+ engine_name 'spree_mobility'
7
+
8
+ config.autoload_paths += %W(#{config.root}/lib)
9
+
10
+ initializer "spree_mobility.environment", before: :load_config_initializers do |app|
11
+ SpreeMobility::Config = SpreeMobility::Configuration.new
12
+ end
13
+
14
+ initializer "spree_mobility.permitted_attributes", before: :load_config_initializers do |app|
15
+ taxon_attributes = { translations_attributes: [:id, :locale, :name, :description, :permalink, :meta_description, :meta_keywords, :meta_title] }
16
+ Spree::PermittedAttributes.taxon_attributes << taxon_attributes
17
+
18
+ option_value_attributes = { translations_attributes: [:id, :locale, :name, :presentation] }
19
+ Spree::PermittedAttributes.option_value_attributes << option_value_attributes
20
+
21
+ store_attributes = { translations_attributes: [:id, :locale, :name, :meta_description, :meta_keywords, :seo_title] }
22
+ Spree::PermittedAttributes.store_attributes << store_attributes
23
+ end
24
+
25
+ def self.activate
26
+ Dir.glob(File.join(root, "app/**/*_decorator*.rb")) do |c|
27
+ Rails.configuration.cache_classes ? require(c) : load(c)
28
+ end
29
+ end
30
+
31
+ config.to_prepare &method(:activate).to_proc
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ module SpreeMobility
2
+ module Fallbacks
3
+ # Prevents the app from breaking when a translation is not present on the
4
+ # default locale. It should search for translations in all supported
5
+ # locales
6
+ #
7
+ # It needs to build a proper key value hash for every locale. So that a locale
8
+ # always fallbacks to itself first before looking at the default and then
9
+ # to any other. e.g
10
+ #
11
+ # supported_locales = [:es, :de, :en]
12
+ #
13
+ # # right
14
+ # { en: [:en, :de, :es], es: [:es, :en, :de] .. }
15
+ #
16
+ # # wrong, spanish locale would fallback to english first
17
+ # { en: [:en, :es], es: [:en, :es] }
18
+ #
19
+ # # wrong, spanish locale would fallback to german first instead of :en (default)
20
+ # { en: [:en, :de, :es], es: [:es, :de, :en] .. }
21
+ #
22
+ def self.config!
23
+ supported = if Spree::Store.respond_to?(:available_locales) && Spree::Store.available_locales.any?
24
+ Spree::Store.available_locales
25
+ else
26
+ Config.supported_locales
27
+ end
28
+ default = I18n.default_locale.to_s
29
+
30
+ fallbacks_map = supported.inject({}) do |fallbacks, locale|
31
+ if locale == default
32
+ fallbacks.merge(locale => (supported-[locale]).flatten)
33
+ else
34
+ fallbacks.merge(locale => [default].push(supported-[locale, default]).flatten)
35
+ end
36
+ end
37
+
38
+ Mobility.configure do
39
+ plugins do
40
+ fallbacks fallbacks_map
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeMobility
2
+ class TranslationQuery
3
+ def initialize(mobility_backend_class)
4
+ @mobility_backend_class = mobility_backend_class
5
+ end
6
+
7
+ def add_joins(search_scope)
8
+ fallback_locales.each do |locale|
9
+ search_scope =
10
+ @mobility_backend_class.
11
+ send(:join_translations, search_scope, locale, ::Arel::Nodes::OuterJoin)
12
+ end
13
+ search_scope
14
+ end
15
+
16
+ def col_name(attr)
17
+ select_columns = []
18
+ fallback_locales.each do |locale|
19
+ select_columns << "#{table_alias(locale)}.#{attr}"
20
+ end
21
+ "COALESCE(#{select_columns.join(',')})"
22
+ end
23
+
24
+ private
25
+
26
+ def fallback_locales
27
+ # Cache this result, since Mobility.locale will not change
28
+ # during this class's lifetime
29
+ @fallback_locales ||= SpreeMobility.locale_with_fallbacks
30
+ end
31
+
32
+ def table_alias(locale)
33
+ @mobility_backend_class.table_alias(locale)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module SpreeMobility
2
+ module_function
3
+
4
+ # Returns the version of the currently loaded SpreeMobility as a
5
+ # <tt>Gem::Version</tt>.
6
+ def version
7
+ Gem::Version.new VERSION::STRING
8
+ end
9
+
10
+ module VERSION
11
+ MAJOR = 1
12
+ MINOR = 0
13
+ TINY = 0
14
+ PRE = nil # 'beta'
15
+
16
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ require 'spree_i18n'
2
+ require 'spree_mobility/engine'
3
+ require 'spree_mobility/version'
4
+ require 'spree_mobility/fallbacks'
5
+ require 'spree_mobility/translation_query'
6
+ require 'deface'
7
+
8
+ module SpreeMobility
9
+ def self.prepend_once(to_klass, klass)
10
+ to_klass.prepend(klass) unless to_klass.ancestors.include?(klass)
11
+ end
12
+
13
+ def self.clear_validations_for(klass, *attrs)
14
+ attrs.each do |attr|
15
+ klass.validators_on(attr).each { |val| val.attributes.delete(attr) }
16
+ end
17
+ end
18
+
19
+ def self.translates_for(klass, *attrs)
20
+ klass.translates(*attrs)
21
+ klass.accepts_nested_attributes_for :translations
22
+ klass.whitelisted_ransackable_associations ||= []
23
+ klass.whitelisted_ransackable_associations << 'translations'
24
+ clear_validations_for(klass, *attrs)
25
+
26
+ # used for preloading only current locale and its fallbacks
27
+ translations_assoc = klass.reflect_on_association(:translations)
28
+ klass.has_many :active_translations,
29
+ -> { where(locale: SpreeMobility.locale_with_fallbacks) },
30
+ class_name: translations_assoc.class_name,
31
+ inverse_of: translations_assoc.inverse_of.name,
32
+ foreign_key: translations_assoc.foreign_key
33
+ end
34
+
35
+ def self.locale_with_fallbacks
36
+ result = [::Mobility.locale]
37
+ begin
38
+ # At the moment the easiest way to access Mobility fallbacks properly
39
+ # is through a translated model's attribute
40
+ backend = ::Spree::Product.mobility_backend_class(:name)
41
+ result.concat(backend.fallbacks[::Mobility.locale])
42
+ result.uniq!
43
+ rescue KeyError # backend not found
44
+ end
45
+ result
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.feature "Products", :js do
2
+ stub_authorization!
3
+ given!(:product) { create(:product) }
4
+ given!(:store) { create(:store) }
5
+
6
+ # Regression Spec: https://github.com/spree/spree_i18n/issues/386
7
+ context "cloning" do
8
+ xscenario "doesnt blow up" do
9
+ visit spree.admin_products_path
10
+ click_icon :clone
11
+
12
+ expect(page).to have_text_like 'has been cloned'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,259 @@
1
+ RSpec.feature "Translations", :js do
2
+ stub_authorization!
3
+
4
+ given(:language) { Spree.t(:this_file_language, scope: 'i18n', locale: 'pt-BR') }
5
+ given!(:store) { create(:store) }
6
+
7
+ background do
8
+ [:en, :'pt-BR'].each do |locale|
9
+ create(:store, default_locale: locale)
10
+ end
11
+ end
12
+
13
+ context "products" do
14
+ given(:product) { create(:product) }
15
+
16
+ context "fills in translations fields" do
17
+ scenario "saves translated attributes properly" do
18
+ visit spree.admin_product_path(product)
19
+ click_on "Translations"
20
+
21
+ within("#attr_fields .name.en") { fill_in_name "Pearl Jam" }
22
+ within("#attr_fields .name.pt-BR") { fill_in_name "Geleia de perola" }
23
+ click_on "Update"
24
+
25
+ visit spree.admin_translations_path('products', product.id)
26
+ select2 'Português (pt-BR)', from: 'Select locale'
27
+
28
+ expect(page).to have_selector("input[value='Geleia de perola']")
29
+ end
30
+ end
31
+
32
+ context "product properties" do
33
+ given!(:product_property) { create(:product_property, value: "red") }
34
+
35
+ xscenario "saves translated attributes properly" do
36
+ visit spree.admin_product_product_properties_path(product_property.product)
37
+ within_row(1) { click_icon :translate }
38
+
39
+ within("#attr_fields .value.pt-BR") { fill_in_name "vermelho" }
40
+ click_on "Update"
41
+ visit spree.admin_product_product_properties_path(product_property.product)
42
+
43
+ expect(page).to have_selector("input[value=vermelho]")
44
+ end
45
+ end
46
+
47
+ context "option types" do
48
+ given!(:option_type) { create(:option_value).option_type }
49
+
50
+ scenario "saves translated attributes properly" do
51
+ visit spree.admin_option_types_path
52
+ within_row(1) { click_icon :translate }
53
+
54
+ within("#attr_fields .name.en") { fill_in_name "shirt sizes" }
55
+ within("#attr_list") { click_on "Presentation" }
56
+ within("#attr_fields .presentation.en") { fill_in_name "size" }
57
+ within("#attr_fields .presentation.pt-BR") { fill_in_name "tamanho" }
58
+ click_on "Update"
59
+ visit spree.admin_translations_path('option_types', option_type.id)
60
+ select2 'Português (pt-BR)', from: 'Select locale'
61
+ click_on 'Presentation'
62
+
63
+ expect(page).to have_selector("input[value=tamanho]")
64
+ end
65
+
66
+ # Regression test for issue #354
67
+ scenario "successfully creates an option type and go to its edit page" do
68
+ visit spree.admin_option_types_path
69
+ click_link "New Option Type"
70
+ fill_in "Name", with: "Shirt Size"
71
+ fill_in "Presentation", with: "Sizes"
72
+ click_button "Create"
73
+
74
+ expect(page).to have_text_like 'has been successfully created'
75
+ expect(page).to have_text_like 'Option Values'
76
+ end
77
+ end
78
+
79
+ context "option values" do
80
+ given!(:option_type) { create(:option_value).option_type }
81
+
82
+ scenario "saves translated attributes properly" do
83
+ visit spree.admin_option_types_path
84
+ within_row(1) { click_icon :translate }
85
+
86
+ within("#attr_fields .name.en") { fill_in_name "big" }
87
+ within("#attr_list") { click_on "Presentation" }
88
+ within("#attr_fields .presentation.en") { fill_in_name "big" }
89
+ within("#attr_fields .presentation.pt-BR") { fill_in_name "grande" }
90
+ click_on "Update"
91
+
92
+ visit spree.admin_translations_path('option_types', option_type.id)
93
+ select2 'Português (pt-BR)', from: 'Select locale'
94
+ click_on 'Presentation'
95
+
96
+ expect(page).to have_selector("input[value=grande]")
97
+ end
98
+ end
99
+
100
+ context "properties" do
101
+ given!(:property) { create(:property) }
102
+
103
+ scenario "saves translated attributes properly" do
104
+ visit spree.admin_properties_path
105
+ within_row(1) { click_icon :translate }
106
+
107
+ within("#attr_fields .name.pt-BR") { fill_in_name "Modelo" }
108
+ within("#attr_list") { click_on "Presentation" }
109
+ within("#attr_fields .presentation.en") { fill_in_name "Model" }
110
+ within("#attr_fields .presentation.pt-BR") { fill_in_name "Modelo" }
111
+ click_on "Update"
112
+ visit spree.admin_translations_path('properties', property.id)
113
+ select2 'Português (pt-BR)', from: 'Select locale'
114
+
115
+ expect(page).to have_selector("input[value=Modelo]")
116
+ end
117
+ end
118
+ end
119
+
120
+ context "promotions" do
121
+ given!(:promotion) { create(:promotion) }
122
+
123
+ scenario "saves translated attributes properly" do
124
+ visit spree.admin_promotions_path
125
+ within_row(1) { click_icon :translate }
126
+
127
+ within("#attr_fields .name.en") { fill_in_name "All free" }
128
+ within("#attr_fields .name.pt-BR") { fill_in_name "Salve salve" }
129
+ click_on "Update"
130
+ visit spree.admin_translations_path('promotions', promotion.id)
131
+ select2 'Português (pt-BR)', from: 'Select locale'
132
+ expect(page).to have_selector("input[value='Salve salve']")
133
+ end
134
+
135
+ it "render edit route properly" do
136
+ visit spree.admin_promotions_path
137
+ within_row(1) { click_icon :translate }
138
+ click_on 'Cancel'
139
+
140
+ expect(page).to have_css('.content-header')
141
+ end
142
+ end
143
+
144
+ context "taxonomies" do
145
+ given!(:taxonomy) { create(:taxonomy) }
146
+
147
+ scenario "saves translated attributes properly" do
148
+ visit spree.admin_taxonomies_path
149
+ within_row(1) { click_icon :translate }
150
+
151
+ within("#attr_fields .name.en") { fill_in_name "Guitars" }
152
+ within("#attr_fields .name.pt-BR") { fill_in_name "Guitarras" }
153
+ click_on "Update"
154
+
155
+ visit spree.admin_translations_path('taxonomies', taxonomy.id)
156
+ select2 'Português (pt-BR)', from: 'Select locale'
157
+
158
+ expect(page).to have_selector("input[value=Guitarras]")
159
+ end
160
+ end
161
+
162
+ context 'taxons' do
163
+ given!(:taxonomy) { create(:taxonomy) }
164
+ given!(:taxon) { create(:taxon, taxonomy: taxonomy, parent_id: taxonomy.root.id) }
165
+
166
+ scenario "saves translated attributes properly" do
167
+ visit spree.admin_translations_path('taxons', taxon.id)
168
+
169
+ within("#attr_fields .name.en") { fill_in_name "Acoustic" }
170
+ within("#attr_fields .name.pt-BR") { fill_in_name "Acusticas" }
171
+ click_on "Update"
172
+
173
+ visit spree.admin_translations_path('taxons', taxon.id)
174
+
175
+ # ensure we're not duplicating translated records on database
176
+ expect {
177
+ click_on "Update"
178
+ }.not_to change { taxon.translations.count }
179
+
180
+ # ensure taxon is in root or it will not be visible
181
+ expect(taxonomy.root.children.count).to be(1)
182
+
183
+ visit spree.admin_translations_path('taxons', taxon.id)
184
+ select2 'Português (pt-BR)', from: 'Select locale'
185
+
186
+ expect(page).to have_selector("input[value=Acusticas]")
187
+ end
188
+ end
189
+
190
+ context "store" do
191
+ scenario 'saves translated attributes properly' do
192
+ visit spree.admin_translations_path('stores', store.id)
193
+
194
+ within("#attr_fields .name.pt-BR") { fill_in_name "nome store" }
195
+
196
+ click_on "Update"
197
+
198
+ visit spree.admin_translations_path('stores', store.id)
199
+ select2 'Português (pt-BR)', from: 'Select locale'
200
+
201
+ expect(page).to have_selector("input[value='nome store']")
202
+ end
203
+ end
204
+
205
+ context "shipping methods" do
206
+ given(:shipping_category) { create(:shipping_category) }
207
+ given!(:shipping_method) { create(:shipping_method, shipping_categories:[shipping_category]) }
208
+
209
+ scenario 'saves translated attributes properly' do
210
+ visit spree.admin_translations_path('shipping_methods', shipping_method.id)
211
+
212
+ within("#attr_fields .name.en") { fill_in_name "Urgent elivery" }
213
+ within("#attr_fields .name.pt-BR") { fill_in_name "Entrega urgente" }
214
+ click_on "Update"
215
+
216
+ visit spree.admin_translations_path('shipping_methods', shipping_method.id)
217
+ select2 'Português (pt-BR)', from: 'Select locale'
218
+ expect(page).to have_selector("input[value='Entrega urgente']")
219
+ end
220
+
221
+ it "render edit route properly" do
222
+ visit spree.admin_shipping_methods_path
223
+ within_row(1) { click_icon :translate }
224
+ click_on 'Cancel'
225
+
226
+ expect(page).to have_css('.content-header')
227
+ end
228
+ end
229
+
230
+ context "permalink routing" do
231
+ given(:language) { Spree.t(:this_file_language, scope: 'i18n', locale: 'de') }
232
+ given(:product) { create(:product) }
233
+
234
+ scenario "finds the right product with permalink in a not active language" do
235
+ [:en, :de].each do |locale|
236
+ create(:store, default_locale: locale)
237
+ end
238
+
239
+ visit spree.admin_product_path(product)
240
+ click_on "Translations"
241
+ click_on "Slug"
242
+ within("#attr_fields .slug.en") { fill_in_name "en_link" }
243
+ within("#attr_fields .slug.de") { fill_in_name "de_link" }
244
+ click_on "Update"
245
+
246
+ visit spree.product_path 'en_link'
247
+ expect(page).to have_text_like 'Product'
248
+
249
+ visit spree.product_path 'de_link'
250
+ expect(page).to have_text_like 'Product'
251
+ end
252
+ end
253
+
254
+ private
255
+
256
+ def fill_in_name(value)
257
+ fill_in first("input[type='text']")["name"], with: value
258
+ end
259
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ RSpec.feature "Translations" do
3
+ before do
4
+ Spree::Store.update_all(default_locale: 'pt-BR')
5
+ end
6
+
7
+ context 'product' do
8
+ let!(:store) { create(:store, default: true, default_locale: 'pt-BR', supported_locales: 'pt-BR', url:'http://www.example.com') }
9
+ let!(:product) do
10
+ create(:product,
11
+ name: 'Antimatter',
12
+ meta_title: 'Antimatter meta_title',
13
+ meta_description: 'Antimatter meta_description',
14
+ translations: [
15
+ Spree::Product::Translation.new(
16
+ locale: 'pt-BR',
17
+ name: 'Antimatéria',
18
+ meta_title: 'Antimatéria meta_title',
19
+ meta_description: 'Antimatéria meta_description'
20
+ )
21
+ ]
22
+ )
23
+ end
24
+
25
+ scenario 'displays translated product page', js: true do
26
+ visit '/products/antimatter'
27
+ expect(page.title).to have_content('Antimatéria')
28
+ end
29
+
30
+ scenario 'displays translated products list', js: true do
31
+ visit '/products'
32
+ expect(page).to have_content('Antimatéria')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+ module Spree
4
+ RSpec.describe Product, type: :model do
5
+ let(:product) { create(:product) }
6
+ let(:taxon) { create(:taxon) }
7
+
8
+ # Regression test for #309
9
+ xit "duplicates translations" do
10
+ original_count = product.translations.count
11
+ new_product = product.duplicate
12
+ expect(new_product.translations).not_to be_blank
13
+ expect(product.translations.count).to eq original_count
14
+ end
15
+
16
+ # Regression test for #433
17
+ it "allow saving a product with taxons" do
18
+ product.taxons << taxon
19
+ expect(product.taxons).to include(taxon)
20
+ end
21
+
22
+ it "handle translation in ransack" do
23
+ result = described_class.ransack(name_cont: product.name[0..2]).result
24
+ expect(result.first).to eq product
25
+
26
+ result = described_class.search(name_cont: product.name[0..2]).result
27
+ expect(result.first).to eq product
28
+ end
29
+
30
+ # Regression tests for #466
31
+ describe ".like_any" do
32
+ context "allow searching products through their translations" do
33
+ before do
34
+ I18n.locale = :"zh-CN"
35
+ end
36
+
37
+ it "with name" do
38
+ product.translations.create locale: "zh-CN",
39
+ name: "创意马克杯",
40
+ description: "<p>一流工程师设计制造手工艺品</p>",
41
+ meta_description: '顶尖工艺设计',
42
+ meta_keywords: '工艺品'
43
+
44
+ expect(Product.like_any([:name], ['创意'])).to include(product)
45
+ end
46
+
47
+ it "with name or description" do
48
+ product.translations.create locale: "zh-CN",
49
+ name: "创意马克杯",
50
+ description: "<p>一流工程师设计制造手工艺品</p>",
51
+ meta_description: '顶尖工艺设计',
52
+ meta_keywords: '工艺品'
53
+
54
+ expect(Product.like_any([:name, :description], ['手工艺品'])).to include(product)
55
+ end
56
+ end
57
+ end
58
+
59
+ context "soft-deletion" do
60
+ subject do
61
+ product.destroy
62
+ product.reload
63
+ end
64
+
65
+ it "keeps the translation on deletion" do
66
+ subject
67
+ expect(product.translations).not_to be_empty
68
+ end
69
+
70
+ it "changes the slug on the translation to allow reuse of original slug" do
71
+ expect do
72
+ subject
73
+ end.to change { product.slug }
74
+ end
75
+ end
76
+ end
77
+ end