spree_mobility 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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