diff --git a/app/controllers/admin/settings/user_experiences_controller.rb b/app/controllers/admin/settings/user_experiences_controller.rb new file mode 100644 index 000000000..53c0b6dc5 --- /dev/null +++ b/app/controllers/admin/settings/user_experiences_controller.rb @@ -0,0 +1,34 @@ +module Admin + module Settings + class UserExperiencesController < Admin::ApplicationController + def create + errors = upsert_config(settings_params) + + if errors.none? + Audit::Logger.log(:internal, current_user, params.dup) + redirect_to admin_config_path, notice: "Site configuration was successfully updated." + else + redirect_to admin_config_path, alert: "😭 #{errors.to_sentence}" + end + end + + def settings_params + params + .require(:settings_user_experience) + .permit(*::Settings::UserExperience.keys) + end + + def upsert_config(configs) + errors = [] + configs.each do |key, value| + ::Settings::UserExperience.public_send("#{key}=", value) if value.present? + rescue ActiveRecord::RecordInvalid => e + errors << e.message + next + end + + errors + end + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 37241ca85..615ee6fd1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -51,7 +51,7 @@ class ApplicationController < ActionController::Base def verify_private_forem return if controller_name.in?(PUBLIC_CONTROLLERS) return if self.class.module_parent.to_s == "Admin" - return if user_signed_in? || SiteConfig.public + return if user_signed_in? || Settings::UserExperience.public if api_action? authenticate! @@ -139,7 +139,7 @@ class ApplicationController < ActionController::Base def feed_style_preference # TODO: Future functionality will let current_user override this value with UX preferences # if current_user exists and has a different preference. - SiteConfig.feed_style + Settings::UserExperience.feed_style end helper_method :feed_style_preference diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb index ab1bf682b..5afbe5ca3 100644 --- a/app/controllers/articles_controller.rb +++ b/app/controllers/articles_controller.rb @@ -32,7 +32,8 @@ class ArticlesController < ApplicationController .includes(:user) else @articles - .where(featured: true).or(@articles.where(score: SiteConfig.home_feed_minimum_score..)) + .where(featured: true) + .or(@articles.where(score: Settings::UserExperience.home_feed_minimum_score..)) .includes(:user) end diff --git a/app/controllers/concerns/caching_headers.rb b/app/controllers/concerns/caching_headers.rb index 11734c1fb..bf2be2683 100644 --- a/app/controllers/concerns/caching_headers.rb +++ b/app/controllers/concerns/caching_headers.rb @@ -18,7 +18,8 @@ module CachingHeaders stale_while_revalidate: nil, stale_if_error: 26_400 ) - return unless SiteConfig.public # Only public forems should be edge-cached based on current functionality. + # Only public forems should be edge-cached based on current functionality. + return unless Settings::UserExperience.public request.session_options[:skip] = true # no cookies diff --git a/app/controllers/stories/feeds_controller.rb b/app/controllers/stories/feeds_controller.rb index 5daf510a5..d6ff0750b 100644 --- a/app/controllers/stories/feeds_controller.rb +++ b/app/controllers/stories/feeds_controller.rb @@ -22,7 +22,7 @@ module Stories end def signed_in_base_feed - if SiteConfig.feed_strategy == "basic" + if Settings::UserExperience.feed_strategy == "basic" Articles::Feeds::Basic.new(user: current_user, page: @page, tag: params[:tag]).feed else optimized_signed_in_feed @@ -30,7 +30,7 @@ module Stories end def signed_out_base_feed - if SiteConfig.feed_strategy == "basic" + if Settings::UserExperience.feed_strategy == "basic" Articles::Feeds::Basic.new(user: nil, page: @page, tag: params[:tag]).feed else Articles::Feeds::LargeForemExperimental.new(user: current_user, page: @page, tag: params[:tag]) diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index e024fb60c..ce582304a 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -135,7 +135,7 @@ class StoriesController < ApplicationController @num_published_articles = if @tag_model.requires_approval? @tag_model.articles.published.where(approved: true).count - elsif SiteConfig.feed_strategy == "basic" + elsif Settings::UserExperience.feed_strategy == "basic" tagged_count else Rails.cache.fetch("article-cached-tagged-count-#{@tag}", expires_in: 2.hours) do @@ -353,7 +353,7 @@ class StoriesController < ApplicationController elsif params[:timeframe] == "latest" @stories.where("score > ?", -20).order(published_at: :desc) else - @stories.order(hotness_score: :desc).where("score >= ?", SiteConfig.home_feed_minimum_score) + @stories.order(hotness_score: :desc).where("score >= ?", Settings::UserExperience.home_feed_minimum_score) end end @@ -485,6 +485,6 @@ class StoriesController < ApplicationController end def tagged_count - @tag_model.articles.published.where("score >= ?", SiteConfig.tag_feed_minimum_score).count + @tag_model.articles.published.where("score >= ?", Settings::UserExperience.tag_feed_minimum_score).count end end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index a39545fec..fc2904fc9 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -50,7 +50,7 @@ class UserDecorator < ApplicationDecorator end def config_font_name - config_font.gsub("default", SiteConfig.default_font) + config_font.gsub("default", Settings::UserExperience.default_font) end def config_body_class diff --git a/app/lib/constants/settings/user_experience.rb b/app/lib/constants/settings/user_experience.rb new file mode 100644 index 000000000..6bbf1228c --- /dev/null +++ b/app/lib/constants/settings/user_experience.rb @@ -0,0 +1,32 @@ +module Constants + module Settings + module UserExperience + DETAILS = { + default_font: { + description: "Determines the default reading font (registered users can change this in their UX settings)" + }, + feed_strategy: { + description: "Determines the main feed algorithm approach the app takes: basic or large_forem_experimental + (which should only be used for 10k+ member communities)", + placeholder: "basic" + }, + feed_style: { + description: "Determines which default feed the users sees (rich content, more minimal, etc.)", + placeholder: "basic, rich, or compact" + }, + home_feed_minimum_score: { + description: "Minimum score needed for a post to show up on the unauthenticated home page.", + placeholder: "0" + }, + primary_brand_color_hex: { + description: "Determines background/border of buttons etc. Must be dark enough to contrast with white text.", + placeholder: "#0a0a0a" + }, + tag_feed_minimum_score: { + description: "Minimum score needed for a post to show up on default tag page.", + placeholder: "0" + } + }.freeze + end + end +end diff --git a/app/lib/constants/site_config.rb b/app/lib/constants/site_config.rb index 65a82807c..c7cdeebcf 100644 --- a/app/lib/constants/site_config.rb +++ b/app/lib/constants/site_config.rb @@ -22,9 +22,6 @@ module Constants placeholder: "" } }, - default_font: { - description: "Determines the default Base Reading Font (registered users can change this in their UX settings)" - }, email_addresses: { description: "Email address", placeholder: "" @@ -33,19 +30,6 @@ module Constants description: "Used as the site favicon", placeholder: IMAGE_PLACEHOLDER }, - feed_strategy: { - description: "Determines the main feed algorithm approach the app takes: basic or large_forem_experimental - (which should only be used for 10k+ member communities)", - placeholder: "basic" - }, - feed_style: { - description: "Determines which default feed the users sees (rich content, more minimal, etc.)", - placeholder: "basic, rich, or compact" - }, - primary_brand_color_hex: { - description: "Determines background/border of buttons etc. Must be dark enough to contrast with white text.", - placeholder: "#0a0a0a" - }, ga_tracking_id: { description: "Google Analytics Tracking ID, e.g. UA-71991000-1", placeholder: "" @@ -54,10 +38,6 @@ module Constants description: "Used to authenticate with your health check endpoints.", placeholder: "a secure token" }, - home_feed_minimum_score: { - description: "Minimum score needed for a post to show up on the unauthenticated home page.", - placeholder: "0" - }, logo_png: { description: "Used as a fallback to the SVG. Recommended minimum of 512x512px for PWA support", placeholder: IMAGE_PLACEHOLDER @@ -148,10 +128,6 @@ module Constants description: "Always show suggested users as suggested people to follow even when " \ "auto-suggestion is available" }, - tag_feed_minimum_score: { - description: "Minimum score needed for a post to show up on default tag page.", - placeholder: "0" - }, twitter_hashtag: { description: "Used as the twitter hashtag of the community", placeholder: "#DEVCommunity" diff --git a/app/models/settings/user_experience.rb b/app/models/settings/user_experience.rb new file mode 100644 index 000000000..0ffb2f9bf --- /dev/null +++ b/app/models/settings/user_experience.rb @@ -0,0 +1,30 @@ +module Settings + # Basic UX settings that can be overriden by individual user preferences. + class UserExperience < RailsSettings::Base + self.table_name = :settings_user_experiences + + HEX_COLOR_REGEX = /\A#(\h{6}|\h{3})\z/.freeze + + # The configuration is cached, change this if you want to force update + # the cache, or call Settings::UserExperience.clear_cache + cache_prefix { "v1" } + + # The default font for all users that have not chosen a custom font yet + field :default_font, type: :string, default: "sans_serif" + field :feed_strategy, type: :string, default: "basic" + # basic (current default), rich (cover image on all posts), compact (more minimal) + field :feed_style, type: :string, default: "basic" + field :home_feed_minimum_score, type: :integer, default: 0 + field :primary_brand_color_hex, type: :string, default: "#3b49df", validates: { + format: { + with: HEX_COLOR_REGEX, + message: "must be be a 3 or 6 character hex (starting with #)" + }, + color_contrast: true + } + # a non-public forem will redirect all unauthenticated pages to the registration page. + # a public forem could have more fine-grained authentication (listings ar private etc.) in future + field :public, type: :boolean, default: 0 + field :tag_feed_minimum_score, type: :integer, default: 0 + end +end diff --git a/app/models/site_config.rb b/app/models/site_config.rb index 1b0f6c41d..acbbe3d24 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -178,6 +178,8 @@ class SiteConfig < RailsSettings::Base # Tags field :sidebar_tags, type: :array, default: %w[] + # NOTE: @citizen428 - These will be removed once we migrated to Settings::UserExperience + # across the whole fleet. # User Experience # These are the default UX settings, which can be overridded by individual user preferences. # basic (current default), rich (cover image on all posts), compact (more minimal) diff --git a/app/services/articles/feeds/large_forem_experimental.rb b/app/services/articles/feeds/large_forem_experimental.rb index be14fe4fb..2e1987fb8 100644 --- a/app/services/articles/feeds/large_forem_experimental.rb +++ b/app/services/articles/feeds/large_forem_experimental.rb @@ -124,7 +124,7 @@ module Articles else hot_stories = Article.published.limited_column_select .page(@page).per(@number_of_articles) - .where("score >= ? OR featured = ?", SiteConfig.home_feed_minimum_score, true) + .where("score >= ? OR featured = ?", Settings::UserExperience.home_feed_minimum_score, true) .order(hotness_score: :desc) featured_story = hot_stories.where.not(main_image: nil).first end diff --git a/app/views/admin/configs/show.html.erb b/app/views/admin/configs/show.html.erb index 97b91daa9..3933440d8 100644 --- a/app/views/admin/configs/show.html.erb +++ b/app/views/admin/configs/show.html.erb @@ -1162,7 +1162,7 @@ <% end %> - <%= form_for(SiteConfig.new, url: admin_config_path) do |f| %> + <%= form_for(Settings::UserExperience.new, url: admin_settings_user_experiences_path) do |f| %>
<%= render partial: "admin/shared/card_header", locals: { @@ -1174,61 +1174,61 @@
- <%= admin_config_label :feed_style %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:feed_style][:description] %> + <%= admin_config_label :feed_style, model: Settings::UserExperience %> + <%= admin_config_description Constants::Settings::UserExperience::DETAILS[:feed_style][:description] %> <%= f.text_field :feed_style, class: "crayons-textfield", - value: SiteConfig.feed_style, - placeholder: Constants::SiteConfig::DETAILS[:feed_style][:placeholder] %> + value: Settings::UserExperience.feed_style, + placeholder: Constants::Settings::UserExperience::DETAILS[:feed_style][:placeholder] %>
- <%= admin_config_label :feed_strategy %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:feed_strategy][:description] %> + <%= admin_config_label :feed_strategy, model: Settings::UserExperience %> + <%= admin_config_description Constants::Settings::UserExperience::DETAILS[:feed_strategy][:description] %> <%= f.text_field :feed_strategy, class: "crayons-textfield", - value: SiteConfig.feed_strategy, - placeholder: Constants::SiteConfig::DETAILS[:feed_strategy][:placeholder] %> + value: Settings::UserExperience.feed_strategy, + placeholder: Constants::Settings::UserExperience::DETAILS[:feed_strategy][:placeholder] %>
- <%= admin_config_label :tag_feed_minimum_score %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:tag_feed_minimum_score][:description] %> + <%= admin_config_label :tag_feed_minimum_score, model: Settings::UserExperience %> + <%= admin_config_description Constants::Settings::UserExperience::DETAILS[:tag_feed_minimum_score][:description] %> <%= f.number_field :tag_feed_minimum_score, class: "crayons-textfield", - value: SiteConfig.tag_feed_minimum_score, - placeholder: Constants::SiteConfig::DETAILS[:tag_feed_minimum_score][:placeholder] %> + value: Settings::UserExperience.tag_feed_minimum_score, + placeholder: Constants::Settings::UserExperience::DETAILS[:tag_feed_minimum_score][:placeholder] %>
- <%= admin_config_label :home_feed_minimum_score %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:home_feed_minimum_score][:description] %> + <%= admin_config_label :home_feed_minimum_score, model: Settings::UserExperience %> + <%= admin_config_description Constants::Settings::UserExperience::DETAILS[:home_feed_minimum_score][:description] %> <%= f.number_field :home_feed_minimum_score, class: "crayons-textfield", - value: SiteConfig.home_feed_minimum_score, - placeholder: Constants::SiteConfig::DETAILS[:home_feed_minimum_score][:placeholder] %> + value: Settings::UserExperience.home_feed_minimum_score, + placeholder: Constants::Settings::UserExperience::DETAILS[:home_feed_minimum_score][:placeholder] %>
- <%= admin_config_label :default_font %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:default_font][:description] %> - <%= select_tag "site_config[default_font]", + <%= admin_config_label :default_font, model: Settings::UserExperience %> + <%= admin_config_description Constants::Settings::UserExperience::DETAILS[:default_font][:description] %> + <%= select_tag "settings_user_experience[default_font]", options_for_select( [%w[sans-serif sans_serif], %w[serif serif], %w[open-dyslexic open_dyslexic]], - SiteConfig.default_font, + Settings::UserExperience.default_font, ), multiple: false, class: "selectpicker" %>
- <%= admin_config_label :primary_brand_color_hex %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:primary_brand_color_hex][:description] %> + <%= admin_config_label :primary_brand_color_hex, model: Settings::UserExperience %> + <%= admin_config_description Constants::Settings::UserExperience::DETAILS[:primary_brand_color_hex][:description] %> <%= f.color_field :primary_brand_color_hex, class: "crayons-color-selector", - value: SiteConfig.primary_brand_color_hex, + value: Settings::UserExperience.primary_brand_color_hex, pattern: "^#+([a-fA-F0-9]{6})$", - placeholder: Constants::SiteConfig::DETAILS[:primary_brand_color_hex][:placeholder] %> + placeholder: Constants::Settings::UserExperience::DETAILS[:primary_brand_color_hex][:placeholder] %>
- <%= f.check_box :public, checked: SiteConfig.public, class: "crayons-checkbox" %> + <%= f.check_box :public, checked: Settings::UserExperience.public, class: "crayons-checkbox" %>
- <%= admin_config_label :public %> + <%= admin_config_label :public, model: Settings::UserExperience %>

Are most of the site pages (e.g. posts, profiles) public? Please take precaution when changing this setting.

diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index ae98ffb96..3bd817aa7 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -48,7 +48,7 @@ <% cache(release_adjusted_cache_key("top-html-and-config--#{user_signed_in?}")) do %> -article-body default-header" + class="<%= Settings::UserExperience.default_font.tr("_", "-") %>-article-body default-header" data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>" data-app-domain="<%= ChatChannel.urlsafe_encoded_app_domain %>" data-honeybadger-key="<%= ApplicationConfig["HONEYBADGER_JS_API_KEY"] %>" @@ -60,10 +60,10 @@
diff --git a/config/routes.rb b/config/routes.rb index 9c6e4e201..404a11876 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -64,6 +64,7 @@ Rails.application.routes.draw do resources :communities, only: [:create] resources :mascots, only: [:create] resources :rate_limits, only: [:create] + resources :user_experiences, only: [:create] end namespace :users do resources :gdpr_delete_requests, only: %i[index destroy] diff --git a/cypress/integration/adminFlows/config/userExperienceSection.js b/cypress/integration/adminFlows/config/userExperienceSection.js new file mode 100644 index 000000000..00ed11d9b --- /dev/null +++ b/cypress/integration/adminFlows/config/userExperienceSection.js @@ -0,0 +1,53 @@ +describe('User experience Section', () => { + beforeEach(() => { + cy.testSetup(); + cy.fixture('users/adminUser.json').as('user'); + + cy.get('@user').then((user) => { + cy.loginUser(user); + }); + }); + + describe('default font', () => { + it('can change the default font', () => { + cy.get('@user').then(({ username }) => { + cy.visit('/admin/config'); + cy.get('#new_settings_user_experience').as('userExperienceSectionForm'); + + cy.get('@userExperienceSectionForm') + .findByText('User Experience and Brand') + .click(); + cy.get('@userExperienceSectionForm') + .get('#settings_user_experience_default_font') + // We need to use force here because the select is covered by another element. + .select('open-dyslexic', { force: true }); + + cy.get('@userExperienceSectionForm') + .findByPlaceholderText('Confirmation text') + .type( + `My username is @${username} and this action is 100% safe and appropriate.`, + ); + + cy.get('@userExperienceSectionForm') + .findByText('Update Site Configuration') + .click(); + + cy.url().should('contains', '/admin/config'); + + cy.findByText('Site configuration was successfully updated.').should( + 'be.visible', + ); + + // Page reloaded so need to get a new reference to the form. + cy.get('#new_settings_user_experience').as('userExperienceSectionForm'); + cy.get('@userExperienceSectionForm') + .findByText('User Experience and Brand') + .click(); + cy.get('#settings_user_experience_default_font').should( + 'have.value', + 'open_dyslexic', + ); + }); + }); + }); +}); diff --git a/db/migrate/20210426021940_create_settings_user_experiences.rb b/db/migrate/20210426021940_create_settings_user_experiences.rb new file mode 100644 index 000000000..505a3e22b --- /dev/null +++ b/db/migrate/20210426021940_create_settings_user_experiences.rb @@ -0,0 +1,16 @@ +class CreateSettingsUserExperiences < ActiveRecord::Migration[6.1] + def self.up + create_table :settings_user_experiences do |t| + t.string :var, null: false + t.text :value, null: true + + t.timestamps + end + + add_index :settings_user_experiences, :var, unique: true + end + + def self.down + drop_table :settings_user_experiences + end +end diff --git a/db/schema.rb b/db/schema.rb index 8587f6760..60eb53686 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1111,6 +1111,14 @@ ActiveRecord::Schema.define(version: 2021_04_26_165234) do t.index ["var"], name: "index_settings_rate_limits_on_var", unique: true end + create_table "settings_user_experiences", force: :cascade do |t| + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.text "value" + t.string "var", null: false + t.index ["var"], name: "index_settings_user_experiences_on_var", unique: true + end + create_table "site_configs", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false diff --git a/db/seeds.rb b/db/seeds.rb index a14ec23b5..3862c6a71 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -15,7 +15,7 @@ puts "Seeding with multiplication factor: #{SEEDS_MULTIPLIER}\n\n" ############################################################################## # Default development site config if different from production scenario -SiteConfig.public = true +Settings::UserExperience.public = true SiteConfig.waiting_on_first_user = false Settings::Authentication.providers = Authentication::Providers.available @@ -24,7 +24,7 @@ Settings::Authentication.providers = Authentication::Providers.available # Put forem into "starter mode" if ENV["MODE"] == "STARTER" - SiteConfig.public = false + Settings::UserExperience.public = false SiteConfig.waiting_on_first_user = true puts "Seeding forem in starter mode to replicate new creator experience" exit # We don't need any models if we're launching things from startup. diff --git a/lib/data_update_scripts/20210426023014_move_user_experience_settings.rb b/lib/data_update_scripts/20210426023014_move_user_experience_settings.rb new file mode 100644 index 000000000..bf9b1dabc --- /dev/null +++ b/lib/data_update_scripts/20210426023014_move_user_experience_settings.rb @@ -0,0 +1,11 @@ +module DataUpdateScripts + class MoveUserExperienceSettings + def run + return if Settings::UserExperience.any? + + Settings::UserExperience.editable_keys.each do |field| + Settings::UserExperience.public_send("#{field}=", SiteConfig.public_send(field)) + end + end + end +end diff --git a/spec/decorators/user_decorator_spec.rb b/spec/decorators/user_decorator_spec.rb index 29234fc71..12dd6453d 100644 --- a/spec/decorators/user_decorator_spec.rb +++ b/spec/decorators/user_decorator_spec.rb @@ -109,14 +109,14 @@ RSpec.describe UserDecorator, type: :decorator do it "replaces 'default' with font configured for the site in SiteConfig" do expect(user.config_font).to eq("default") %w[sans_serif serif open_dyslexic].each do |font| - allow(SiteConfig).to receive(:default_font).and_return(font) + allow(Settings::UserExperience).to receive(:default_font).and_return(font) expect(user.decorate.config_font_name).to eq(font) end end it "doesn't replace the user's custom selected font" do user_comic_sans = create(:user, config_font: "comic_sans") - allow(SiteConfig).to receive(:default_font).and_return("open_dyslexic") + allow(Settings::UserExperience).to receive(:default_font).and_return("open_dyslexic") expect(user_comic_sans.decorate.config_font_name).to eq("comic_sans") end end diff --git a/spec/lib/data_update_scripts/move_user_experience_settings_spec.rb b/spec/lib/data_update_scripts/move_user_experience_settings_spec.rb new file mode 100644 index 000000000..250a2ceb3 --- /dev/null +++ b/spec/lib/data_update_scripts/move_user_experience_settings_spec.rb @@ -0,0 +1,17 @@ +require "rails_helper" +require Rails.root.join( + "lib/data_update_scripts/20210426023014_move_user_experience_settings.rb", +) + +describe DataUpdateScripts::MoveUserExperienceSettings do + it "moves existing settings" do + allow(SiteConfig).to receive(:default_font).and_return("Comic Sans") + allow(SiteConfig).to receive(:primary_brand_color_hex).and_return("#000") + + expect do + described_class.new.run + end + .to change(Settings::UserExperience, :default_font).to("Comic Sans") + .and change(Settings::UserExperience, :primary_brand_color_hex).to("#000") + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b0d5a2751..2f633862e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -685,6 +685,10 @@ RSpec.describe User, type: :model do end describe "theming properties" do + before do + allow(Settings::UserExperience).to receive(:default_font).and_return("sans-serif") + end + it "creates proper body class with defaults" do classes = "default sans-serif-article-body trusted-status-#{user.trusted} #{user.config_navbar}-header" expect(user.decorate.config_body_class).to eq(classes) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 8b0ac8738..a48ff8378 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -185,7 +185,7 @@ RSpec.configure do |config| }).to_return(status: 200, body: "", headers: {}) allow(Settings::Community).to receive(:community_description).and_return("Some description") - allow(SiteConfig).to receive(:public).and_return(true) + allow(Settings::UserExperience).to receive(:public).and_return(true) allow(SiteConfig).to receive(:waiting_on_first_user).and_return(false) # Default to have field a field test available. diff --git a/spec/requests/admin/configs_spec.rb b/spec/requests/admin/configs_spec.rb index 36d9d104a..2b8894bde 100644 --- a/spec/requests/admin/configs_spec.rb +++ b/spec/requests/admin/configs_spec.rb @@ -851,66 +851,82 @@ RSpec.describe "/admin/config", type: :request do describe "User Experience" do it "updates the feed_style" do feed_style = "basic" - post "/admin/config", params: { site_config: { feed_style: feed_style }, - confirmation: confirmation_message } - expect(SiteConfig.feed_style).to eq(feed_style) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { feed_style: feed_style }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.feed_style).to eq(feed_style) end it "updates the feed_strategy" do feed_strategy = "optimized" - post "/admin/config", params: { site_config: { feed_strategy: feed_strategy }, - confirmation: confirmation_message } - expect(SiteConfig.feed_strategy).to eq(feed_strategy) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { feed_strategy: feed_strategy }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.feed_strategy).to eq(feed_strategy) end it "updates the tag_feed_minimum_score" do tag_feed_minimum_score = 3 - post "/admin/config", params: { site_config: { tag_feed_minimum_score: tag_feed_minimum_score }, - confirmation: confirmation_message } - expect(SiteConfig.tag_feed_minimum_score).to eq(tag_feed_minimum_score) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { tag_feed_minimum_score: tag_feed_minimum_score }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.tag_feed_minimum_score).to eq(tag_feed_minimum_score) end it "updates the home_feed_minimum_score" do home_feed_minimum_score = 5 - post "/admin/config", params: { site_config: { home_feed_minimum_score: home_feed_minimum_score }, - confirmation: confirmation_message } - expect(SiteConfig.home_feed_minimum_score).to eq(home_feed_minimum_score) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { home_feed_minimum_score: home_feed_minimum_score }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.home_feed_minimum_score).to eq(home_feed_minimum_score) end it "updates the brand color if proper hex" do hex = "#0a0a0a" # dark enough - post "/admin/config", params: { site_config: { primary_brand_color_hex: hex }, - confirmation: confirmation_message } - expect(SiteConfig.primary_brand_color_hex).to eq(hex) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { primary_brand_color_hex: hex }, + on: confirmation_message + } + expect(Settings::UserExperience.primary_brand_color_hex).to eq(hex) end it "does not update brand color if hex not contrasting enough" do hex = "#bd746f" # not dark enough post "/admin/config", params: { site_config: { primary_brand_color_hex: hex }, confirmation: confirmation_message } - expect(SiteConfig.primary_brand_color_hex).not_to eq(hex) + expect(Settings::UserExperience.primary_brand_color_hex).not_to eq(hex) end it "does not update brand color if hex not a hex with proper format" do hex = "0a0a0a" # dark enough, but not proper format - post "/admin/config", params: { site_config: { primary_brand_color_hex: hex }, - confirmation: confirmation_message } - expect(SiteConfig.primary_brand_color_hex).not_to eq(hex) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { primary_brand_color_hex: hex }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.primary_brand_color_hex).not_to eq(hex) end it "updates public to true" do is_public = true - post "/admin/config", params: { site_config: { public: is_public }, - confirmation: confirmation_message } - expect(SiteConfig.public).to eq(is_public) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { public: is_public }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.public).to eq(is_public) end it "updates public to false" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) is_public = false - post "/admin/config", params: { site_config: { public: is_public }, - confirmation: confirmation_message } - expect(SiteConfig.public).to eq(is_public) + post admin_settings_user_experiences_path, params: { + settings_user_experience: { public: is_public }, + confirmation: confirmation_message + } + expect(Settings::UserExperience.public).to eq(is_public) end end diff --git a/spec/requests/api/v0/users_spec.rb b/spec/requests/api/v0/users_spec.rb index 339379a0b..94df4a30a 100644 --- a/spec/requests/api/v0/users_spec.rb +++ b/spec/requests/api/v0/users_spec.rb @@ -27,7 +27,7 @@ RSpec.describe "Api::V0::Users", type: :request do end it "returns unauthenticated if no authentication and site config is set to private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get api_user_path("by_username"), params: { url: user.username } expect(response).to have_http_status(:unauthorized) end @@ -78,7 +78,7 @@ RSpec.describe "Api::V0::Users", type: :request do end it "returns 200 if no authentication and site config is set to private but user is authenticated" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get me_api_users_path, params: { access_token: access_token.token } response_user = response.parsed_body diff --git a/spec/requests/articles/articles_spec.rb b/spec/requests/articles/articles_spec.rb index ffa5e993a..2bdd5a55c 100644 --- a/spec/requests/articles/articles_spec.rb +++ b/spec/requests/articles/articles_spec.rb @@ -65,7 +65,9 @@ RSpec.describe "Articles", type: :request do context "when :username param is not given" do let!(:featured_article) { create(:article, featured: true) } - let!(:not_featured_article) { create(:article, featured: false, score: SiteConfig.home_feed_minimum_score - 1) } + let!(:not_featured_article) do + create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score - 1) + end before { get feed_path } @@ -157,25 +159,25 @@ RSpec.describe "Articles", type: :request do context "with scored articles" do before do - allow(SiteConfig).to receive(:home_feed_minimum_score).and_return(10) + allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(10) end - it "does not contain non featured articles with a score below SiteConfig.home_feed_minimum_score" do - create(:article, featured: false, score: SiteConfig.home_feed_minimum_score - 1) + it "does not contain non featured articles with a score below Settings::UserExperience.home_feed_minimum_score" do + create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score - 1) expect { get feed_path }.to raise_error(ActiveRecord::RecordNotFound) end - it "contains non featured articles with a score equal to SiteConfig.home_feed_minimum_score" do - create(:article, featured: false, score: SiteConfig.home_feed_minimum_score) + it "contains non featured articles with a score equal to Settings::UserExperience.home_feed_minimum_score" do + create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score) get feed_path expect(response).to have_http_status(:ok) end - it "contains non featured articles with a score above SiteConfig.home_feed_minimum_score" do - create(:article, featured: false, score: SiteConfig.home_feed_minimum_score + 1) + it "contains non featured articles with a score above Settings::UserExperience.home_feed_minimum_score" do + create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score + 1) get feed_path diff --git a/spec/requests/async_info_spec.rb b/spec/requests/async_info_spec.rb index b3b619e08..26f4f5509 100644 --- a/spec/requests/async_info_spec.rb +++ b/spec/requests/async_info_spec.rb @@ -15,7 +15,7 @@ RSpec.describe "AsyncInfo", type: :request do end it "renders normal response even if site config is private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get "/async_info/base_data" expect(response.parsed_body.keys).to match_array(%w[broadcast param token]) end diff --git a/spec/requests/ga_events_spec.rb b/spec/requests/ga_events_spec.rb index bbb02c64e..20596eace 100644 --- a/spec/requests/ga_events_spec.rb +++ b/spec/requests/ga_events_spec.rb @@ -15,7 +15,7 @@ RSpec.describe "GaEvents", type: :request, vcr: vcr_option do end it "renders normal response even if site config is private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) post "/fallback_activity_recorder", params: { path: "/ben", user_language: "en" }.to_json diff --git a/spec/requests/invitations_spec.rb b/spec/requests/invitations_spec.rb index 2a5b88ca1..82002dd03 100644 --- a/spec/requests/invitations_spec.rb +++ b/spec/requests/invitations_spec.rb @@ -5,7 +5,7 @@ RSpec.describe "Invitations", type: :request do describe "Accept invitation" do it "renders normal response even if site config is private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get "/users/invitation/accept?invitation_token=blahblahblahblah" # This is a fake token, so the only thing we're testing for here is # that we *do not* land on the "registrations" page which shouldn't diff --git a/spec/requests/stories/feeds_spec.rb b/spec/requests/stories/feeds_spec.rb index c1c1e6f6b..7dfa3a654 100644 --- a/spec/requests/stories/feeds_spec.rb +++ b/spec/requests/stories/feeds_spec.rb @@ -31,7 +31,7 @@ RSpec.describe "Stories::Feeds", type: :request do end it "returns feed when feed_strategy is basic" do - allow(SiteConfig).to receive(:feed_strategy).and_return("basic") + allow(Settings::UserExperience).to receive(:feed_strategy).and_return("basic") get "/stories/feed" expect(response_article).to include( "id" => article.id, @@ -45,7 +45,7 @@ RSpec.describe "Stories::Feeds", type: :request do end it "returns feed when feed_strategy is optimized" do - allow(SiteConfig).to receive(:feed_strategy).and_return("optimized") + allow(Settings::UserExperience).to receive(:feed_strategy).and_return("optimized") get "/stories/feed" expect(response_article).to include( "id" => article.id, @@ -128,7 +128,7 @@ RSpec.describe "Stories::Feeds", type: :request do end it "returns feed when feed_strategy is basic" do - allow(SiteConfig).to receive(:feed_strategy).and_return("basic") + allow(Settings::UserExperience).to receive(:feed_strategy).and_return("basic") get "/stories/feed" expect(response_article).to include( "id" => article.id, @@ -142,7 +142,7 @@ RSpec.describe "Stories::Feeds", type: :request do end it "returns feed when feed_strategy is optimized" do - allow(SiteConfig).to receive(:feed_strategy).and_return("optimized") + allow(Settings::UserExperience).to receive(:feed_strategy).and_return("optimized") get "/stories/feed" expect(response_article).to include( "id" => article.id, diff --git a/spec/requests/stories_index_spec.rb b/spec/requests/stories_index_spec.rb index 399d34c07..227820c5a 100644 --- a/spec/requests/stories_index_spec.rb +++ b/spec/requests/stories_index_spec.rb @@ -43,7 +43,7 @@ RSpec.describe "StoriesIndex", type: :request do end it "renders registration page if site config is private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get root_path expect(response.body).to include("Continue with") @@ -105,7 +105,7 @@ RSpec.describe "StoriesIndex", type: :request do end it "does not set cache-related headers if private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get "/" expect(response.status).to eq(200) @@ -148,7 +148,7 @@ RSpec.describe "StoriesIndex", type: :request do it "shows only one cover if basic feed style" do create_list(:article, 3, featured: true, score: 20, main_image: "https://example.com/image.jpg") - allow(SiteConfig).to receive(:feed_style).and_return("basic") + allow(Settings::UserExperience).to receive(:feed_style).and_return("basic") get "/" expect(response.body.scan(/(?=class="crayons-story__cover crayons-story__cover__image)/).count).to be 1 end @@ -156,7 +156,7 @@ RSpec.describe "StoriesIndex", type: :request do it "shows multiple cover images if rich feed style" do create_list(:article, 3, featured: true, score: 20, main_image: "https://example.com/image.jpg") - allow(SiteConfig).to receive(:feed_style).and_return("rich") + allow(Settings::UserExperience).to receive(:feed_style).and_return("rich") get "/" expect(response.body.scan(/(?=class="crayons-story__cover crayons-story__cover__image)/).count).to be > 1 end @@ -199,7 +199,7 @@ RSpec.describe "StoriesIndex", type: :request do context "with campaign_sidebar" do before do allow(Settings::Campaign).to receive(:featured_tags).and_return("mytag,yourtag") - allow(SiteConfig).to receive(:home_feed_minimum_score).and_return(7) + allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(7) a_body = "---\ntitle: Super-sheep#{rand(1000)}\npublished: true\ntags: heyheyhey,mytag\n---\n\nHello" create(:article, approved: true, body_markdown: a_body, score: 1) @@ -402,7 +402,7 @@ RSpec.describe "StoriesIndex", type: :request do end it "renders properly even if site config is private" do - allow(SiteConfig).to receive(:public).and_return(false) + allow(Settings::UserExperience).to receive(:public).and_return(false) get "/t/#{tag.name}" expect(response.body).to include("crayons-tabs__item crayons-tabs__item--current") end diff --git a/spec/services/articles/feeds/large_forem_experimental_spec.rb b/spec/services/articles/feeds/large_forem_experimental_spec.rb index 244e3de7d..874d8fd9e 100644 --- a/spec/services/articles/feeds/large_forem_experimental_spec.rb +++ b/spec/services/articles/feeds/large_forem_experimental_spec.rb @@ -59,7 +59,7 @@ RSpec.describe Articles::Feeds::LargeForemExperimental, type: :service do before do article.update(published_at: 1.week.ago) - allow(SiteConfig).to receive(:home_feed_minimum_score).and_return(0) + allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(0) end it "returns a featured article and correctly scored other articles", :aggregate_failures do diff --git a/spec/support/seeds/seeds_e2e.rb b/spec/support/seeds/seeds_e2e.rb index 3310fc6fc..d0833b3ac 100644 --- a/spec/support/seeds/seeds_e2e.rb +++ b/spec/support/seeds/seeds_e2e.rb @@ -9,7 +9,7 @@ seeder = Seeder.new ############################################################################## # Default development site config if different from production scenario -SiteConfig.public = true +Settings::UserExperience.public = true SiteConfig.waiting_on_first_user = false ##############################################################################