diff --git a/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs b/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs index a6bdfc2183e..82e174efe77 100644 --- a/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs +++ b/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs @@ -34,17 +34,36 @@ {{i18n "admin.user_fields.requirement.optional.title"}} + + + + {{i18n + "admin.user_fields.requirement.for_all_users.title" + }} + {{i18n + "admin.user_fields.requirement.for_all_users.description" + }} + + + {{i18n "admin.user_fields.requirement.on_signup.title"}} @@ -57,7 +76,11 @@ - + {{i18n "admin.user_fields.editable.title"}} diff --git a/app/assets/javascripts/admin/addon/components/admin-user-field-item.js b/app/assets/javascripts/admin/addon/components/admin-user-field-item.js index bcc05298901..d44fcd46512 100644 --- a/app/assets/javascripts/admin/addon/components/admin-user-field-item.js +++ b/app/assets/javascripts/admin/addon/components/admin-user-field-item.js @@ -3,6 +3,7 @@ import { action } from "@ember/object"; import { schedule } from "@ember/runloop"; import { service } from "@ember/service"; import { isEmpty } from "@ember/utils"; +import { Promise } from "rsvp"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { i18n, propertyEqual } from "discourse/lib/computed"; import { bufferedProperty } from "discourse/mixins/buffered-content"; @@ -12,6 +13,7 @@ import UserField from "admin/models/user-field"; export default Component.extend(bufferedProperty("userField"), { adminCustomUserFields: service(), + dialog: service(), tagName: "", isEditing: false, @@ -64,8 +66,29 @@ export default Component.extend(bufferedProperty("userField"), { return ret.join(", "); }, + @discourseComputed("buffered.requirement") + editableDisabled(requirement) { + return requirement === "for_all_users"; + }, + @action - save() { + changeRequirementType(requirement) { + this.buffered.set("requirement", requirement); + this.buffered.set("editable", requirement === "for_all_users"); + }, + + async _confirmChanges() { + return new Promise((resolve) => { + this.dialog.yesNoConfirm({ + message: I18n.t("admin.user_fields.requirement.confirmation"), + didCancel: () => resolve(false), + didConfirm: () => resolve(true), + }); + }); + }, + + @action + async save() { const attrs = this.buffered.getProperties( "name", "description", @@ -79,6 +102,16 @@ export default Component.extend(bufferedProperty("userField"), { ...this.adminCustomUserFields.additionalProperties ); + let confirm = true; + + if (attrs.requirement === "for_all_users") { + confirm = await this._confirmChanges(); + } + + if (!confirm) { + return; + } + return this.userField .save(attrs) .then(() => { diff --git a/app/assets/javascripts/discourse/app/components/user-menu/profile-tab-content.hbs b/app/assets/javascripts/discourse/app/components/user-menu/profile-tab-content.hbs index 63a0d7bf90c..ead9b49845e 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/profile-tab-content.hbs +++ b/app/assets/javascripts/discourse/app/components/user-menu/profile-tab-content.hbs @@ -69,7 +69,7 @@ {{d-icon "cog"}} - {{i18n "user.preferences"}} + {{i18n "user.preferences.title"}} diff --git a/app/assets/javascripts/discourse/app/components/user-nav.hbs b/app/assets/javascripts/discourse/app/components/user-nav.hbs index 4ee9b9831ad..d76a28b0dfe 100644 --- a/app/assets/javascripts/discourse/app/components/user-nav.hbs +++ b/app/assets/javascripts/discourse/app/components/user-nav.hbs @@ -74,7 +74,7 @@ class="user-nav__preferences" > {{d-icon "cog"}} - {{i18n "user.preferences"}} + {{i18n "user.preferences.title"}} {{/if}} {{#if (and @isMobileView @isStaff)}} diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js index 9ffb1cdab96..6adc744772c 100644 --- a/app/assets/javascripts/discourse/app/controllers/preferences/profile.js +++ b/app/assets/javascripts/discourse/app/controllers/preferences/profile.js @@ -42,6 +42,13 @@ export default Controller.extend({ return; } + if (this.showEnforcedRequiredFieldsNotice) { + return this._missingRequiredFields( + this.site.user_fields, + this.model.user_fields + ); + } + // Staff can edit fields that are not `editable` if (!this.currentUser.staff) { siteUserFields = siteUserFields.filterBy("editable", true); @@ -53,6 +60,11 @@ export default Controller.extend({ }); }, + @discourseComputed("currentUser.needs_required_fields_check") + showEnforcedRequiredFieldsNotice(needsRequiredFieldsCheck) { + return needsRequiredFieldsCheck; + }, + @discourseComputed("model.user_option.default_calendar") canChangeDefaultCalendar(defaultCalendar) { return defaultCalendar !== "none_selected"; @@ -81,6 +93,16 @@ export default Controller.extend({ document.querySelector(".feature-topic-on-profile-btn")?.focus(); }, + _missingRequiredFields(siteFields, userFields) { + return siteFields + .filter( + (siteField) => + siteField.requirement === "for_all_users" && + isEmpty(userFields[siteField.id]) + ) + .map((field) => EmberObject.create({ field, value: "" })); + }, + actions: { clearFeaturedTopicFromProfile() { this.dialog.yesNoConfirm({ @@ -132,6 +154,7 @@ export default Controller.extend({ .then(() => { model.set("bio_cooked"); this.set("saved", true); + this.currentUser.set("needs_required_fields_check", false); }) .catch(popupAjaxError); }) diff --git a/app/assets/javascripts/discourse/app/routes/preferences-profile.js b/app/assets/javascripts/discourse/app/routes/preferences-profile.js index 0ac07d167af..56d75a99c37 100644 --- a/app/assets/javascripts/discourse/app/routes/preferences-profile.js +++ b/app/assets/javascripts/discourse/app/routes/preferences-profile.js @@ -1,7 +1,26 @@ +import { action } from "@ember/object"; +import { service } from "@ember/service"; import RestrictedUserRoute from "discourse/routes/restricted-user"; export default class PreferencesProfile extends RestrictedUserRoute { + @service currentUser; + setupController(controller, model) { controller.set("model", model); } + + @action + willTransition(transition) { + super.willTransition(...arguments); + + if ( + this.currentUser?.needs_required_fields_check && + !transition?.to.name.startsWith("admin") + ) { + transition.abort(); + return false; + } + + return true; + } } diff --git a/app/assets/javascripts/discourse/app/routes/preferences.js b/app/assets/javascripts/discourse/app/routes/preferences.js index 3b521b39255..02ddb803938 100644 --- a/app/assets/javascripts/discourse/app/routes/preferences.js +++ b/app/assets/javascripts/discourse/app/routes/preferences.js @@ -13,7 +13,7 @@ export default class Preferences extends RestrictedUserRoute { let controller = this.controllerFor(this.router.currentRouteName); let subpageTitle = controller?.subpageTitle; return subpageTitle - ? `${subpageTitle} - ${I18n.t("user.preferences")}` - : I18n.t("user.preferences"); + ? `${subpageTitle} - ${I18n.t("user.preferences.title")}` + : I18n.t("user.preferences.title"); } } diff --git a/app/assets/javascripts/discourse/app/templates/preferences/profile.hbs b/app/assets/javascripts/discourse/app/templates/preferences/profile.hbs index 2317cf9726c..d8813d5f4ae 100644 --- a/app/assets/javascripts/discourse/app/templates/preferences/profile.hbs +++ b/app/assets/javascripts/discourse/app/templates/preferences/profile.hbs @@ -1,58 +1,66 @@ -{{#if this.canChangeBio}} - - {{i18n "user.bio"}} - - - - +{{#if this.showEnforcedRequiredFieldsNotice}} + {{i18n + "user.preferences.profile.enforced_required_fields" + }} {{/if}} - - {{i18n "user.timezone"}} - - - - -{{#if this.model.can_change_location}} - - {{i18n - "user.location" - }} - - +{{#unless this.showEnforcedRequiredFieldsNotice}} + {{#if this.canChangeBio}} + + {{i18n "user.bio"}} + + + - -{{/if}} + {{/if}} -{{#if this.model.can_change_website}} - - {{i18n - "user.website" - }} - - - + + {{i18n "user.timezone"}} + + -{{/if}} + + {{#if this.model.can_change_location}} + + {{i18n + "user.location" + }} + + + + + {{/if}} + + {{#if this.model.can_change_website}} + + {{i18n + "user.website" + }} + + + + + {{/if}} +{{/unless}} {{#each this.userFields as |uf|}} @@ -61,128 +69,133 @@ {{/each}} -{{#if this.siteSettings.allow_profile_backgrounds}} - {{#if this.canUploadProfileHeader}} - - {{i18n - "user.change_profile_background.title" - }} - - +{{#unless this.showEnforcedRequiredFieldsNotice}} + {{#if this.siteSettings.allow_profile_backgrounds}} + {{#if this.canUploadProfileHeader}} + + {{i18n + "user.change_profile_background.title" + }} + + + + + {{i18n "user.change_profile_background.instructions"}} + - - {{i18n "user.change_profile_background.instructions"}} - - - {{/if}} - {{#if this.canUploadUserCardBackground}} - - {{i18n - "user.change_card_background.title" - }} - - - - - {{i18n "user.change_card_background.instructions"}} - - - {{/if}} -{{/if}} - -{{#if this.siteSettings.allow_featured_topic_on_user_profiles}} - - {{i18n "user.featured_topic"}} - {{#if this.model.featured_topic}} - - - {{replace-emoji (html-safe this.model.featured_topic.fancy_title)}} - - {{/if}} + {{#if this.canUploadUserCardBackground}} + + {{i18n + "user.change_card_background.title" + }} + + + + + {{i18n "user.change_card_background.instructions"}} + + + {{/if}} + {{/if}} - - + {{#if this.siteSettings.allow_featured_topic_on_user_profiles}} + + {{i18n "user.featured_topic"}} {{#if this.model.featured_topic}} - + + + {{replace-emoji (html-safe this.model.featured_topic.fancy_title)}} + + {{/if}} + + + + {{#if this.model.featured_topic}} + + {{/if}} + + + {{i18n "user.change_featured_topic.instructions"}} + - - {{i18n "user.change_featured_topic.instructions"}} + {{/if}} + + {{#if this.canChangeDefaultCalendar}} + + {{i18n + "download_calendar.default_calendar" + }} + + + + + {{i18n "download_calendar.default_calendar_instruction"}} + - -{{/if}} + {{/if}} -{{#if this.canChangeDefaultCalendar}} - - {{i18n - "download_calendar.default_calendar" - }} - - - - - {{i18n "download_calendar.default_calendar_instruction"}} - - -{{/if}} + + + - - - + + + - - - + - - - - - + + + +{{/unless}} { saved_change_to_requirement? } after_save :queue_index_search scope :public_fields, -> { where(show_on_profile: true).or(where(show_on_user_card: true)) } + scope :required, -> { not_optional } enum :requirement, { optional: 0, for_all_users: 1, on_signup: 2 }.freeze enum :field_type_enum, { text: 0, confirm: 1, dropdown: 2, multiselect: 3 }.freeze @@ -37,6 +40,13 @@ class UserField < ActiveRecord::Base private + def update_required_fields_version + return if !for_all_users? + + UserRequiredFieldsVersion.create + Discourse.request_refresh! + end + def sanitize_description if description_changed? self.description = sanitize_field(self.description, additional_attributes: ["target"]) diff --git a/app/models/user_history.rb b/app/models/user_history.rb index 78146253b12..4377ef00c42 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -144,6 +144,8 @@ class UserHistory < ActiveRecord::Base create_watched_word_group: 105, update_watched_word_group: 106, delete_watched_word_group: 107, + redirected_to_required_fields: 108, + filled_in_required_fields: 109, ) end diff --git a/app/models/user_required_fields_version.rb b/app/models/user_required_fields_version.rb new file mode 100644 index 00000000000..13c772ba8da --- /dev/null +++ b/app/models/user_required_fields_version.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class UserRequiredFieldsVersion < ActiveRecord::Base + def self.current = maximum(:id) || 0 +end + +# == Schema Information +# +# Table name: user_required_fields_versions +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index d9e972ddbbf..3d1a6e9ae43 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -58,6 +58,7 @@ class CurrentUserSerializer < BasicUserSerializer :associated_account_ids, :top_category_ids, :groups, + :needs_required_fields_check?, :second_factor_enabled, :ignored_users, :featured_topic, diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index 5f7f6272732..a2e28d2d612 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -266,6 +266,13 @@ class UserUpdater end end DiscourseEvent.trigger(:user_updated, user) + + if attributes[:custom_fields].present? && user.needs_required_fields_check? + UserHistory.create!( + action: UserHistory.actions[:filled_in_required_fields], + acting_user_id: user.id, + ) + end end saved diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 8eeab0f02bd..48373b6d9a1 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1197,7 +1197,10 @@ en: activity_stream: "Activity" read: "Read" read_help: "Recently read topics" - preferences: "Preferences" + preferences: + title: "Preferences" + profile: + enforced_required_fields: "You are required to provide additional information before continuing to use this site." feature_topic_on_profile: open_search: "Select a New Topic" title: "Select a Topic" @@ -6669,9 +6672,13 @@ en: title: "Field Requirement" optional: title: "Optional" + for_all_users: + title: "For all users" + description: "When new users sign up, they must fill out this field. When existing users return to the site and this is a new required field for them, they will also be prompted to fill it out. To re-prompt all users, delete this custom field and re-create it." on_signup: title: "On signup" description: "When new users sign up, they must fill out this field. Existing users are unaffected." + confirmation: "This will prompt existing users to fill in this field and will not allow them to do anything else on your site until the field is filled. Proceed?" editable: title: "Editable after signup" enabled: "editable" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 6a44e7130d8..1a9389e8bb7 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2931,7 +2931,7 @@ en: email_too_long: "The email you provided is too long. Mailbox names must be no more than 254 characters, and domain names must be no more than 253 characters." wrong_invite_code: "The invite code you entered was incorrect." reserved_username: "That username is not allowed." - missing_user_field: "You have not completed all the user fields" + missing_user_field: "You have not completed all the required user fields" auth_complete: "Authentication is complete." click_to_continue: "Click here to continue." already_logged_in: "Sorry! This invitation is intended for new users, who do not already have an existing account." diff --git a/db/migrate/20240531053226_create_user_required_fields_version.rb b/db/migrate/20240531053226_create_user_required_fields_version.rb new file mode 100644 index 00000000000..c90090aef03 --- /dev/null +++ b/db/migrate/20240531053226_create_user_required_fields_version.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class CreateUserRequiredFieldsVersion < ActiveRecord::Migration[7.0] + def change + create_table :user_required_fields_versions do |t| + t.timestamps null: false + end + + add_column :users, :required_fields_version, :integer + end +end diff --git a/plugins/chat/app/controllers/chat/incoming_webhooks_controller.rb b/plugins/chat/app/controllers/chat/incoming_webhooks_controller.rb index df97db10270..7d5d33b0edc 100644 --- a/plugins/chat/app/controllers/chat/incoming_webhooks_controller.rb +++ b/plugins/chat/app/controllers/chat/incoming_webhooks_controller.rb @@ -8,7 +8,9 @@ module Chat WEBHOOK_MESSAGES_PER_MINUTE_LIMIT = 10 - skip_before_action :verify_authenticity_token, :redirect_to_login_if_required + skip_before_action :verify_authenticity_token, + :redirect_to_login_if_required, + :redirect_to_profile_if_required before_action :validate_payload diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4b06b324de6..f7b1ee1d0f8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -3552,4 +3552,45 @@ RSpec.describe User do expect(user.new_personal_messages_notifications_count).to eq(1) end end + + describe "#populated_required_fields?" do + let!(:required_field) { Fabricate(:user_field, name: "hairstyle") } + let!(:optional_field) { Fabricate(:user_field, name: "haircolor", requirement: "optional") } + + context "when all required fields are populated" do + before { user.set_user_field(required_field.id, "bald") } + + it { expect(user.populated_required_custom_fields?).to eq(true) } + end + + context "when some required fields are missing values" do + it { expect(user.populated_required_custom_fields?).to eq(false) } + end + end + + describe "#needs_required_fields_check?" do + let!(:version) { UserRequiredFieldsVersion.create! } + + context "when version number is up to date" do + before { user.update(required_fields_version: version.id) } + + it { expect(user.needs_required_fields_check?).to eq(false) } + end + + context "when version number is out of date" do + before { user.update(required_fields_version: version.id - 1) } + + it { expect(user.needs_required_fields_check?).to eq(true) } + end + end + + describe "#bump_required_fields_version" do + let!(:version) { UserRequiredFieldsVersion.create! } + + it do + expect { user.bump_required_fields_version }.to change { user.required_fields_version }.to( + version.id, + ) + end + end end diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb index 4eaf6b5a1b8..ef52c015f3e 100644 --- a/spec/requests/application_controller_spec.rb +++ b/spec/requests/application_controller_spec.rb @@ -256,6 +256,45 @@ RSpec.describe ApplicationController do end end + describe "#redirect_to_profile_if_required" do + fab!(:user) + + before { sign_in(user) } + + context "when the user is missing required custom fields" do + before do + Fabricate(:user_field, requirement: "for_all_users") + UserRequiredFieldsVersion.create! + end + + it "redirects the user to the profile preferences" do + get "/hot" + expect(response).to redirect_to("/u/#{user.username}/preferences/profile") + end + + it "only logs user history once per day" do + expect do + RateLimiter.enable + get "/hot" + get "/hot" + end.to change { UserHistory.count }.by(1) + end + end + + context "when the user has filled up all required custom fields" do + before do + Fabricate(:user_field, requirement: "for_all_users") + UserRequiredFieldsVersion.create! + user.bump_required_fields_version + end + + it "redirects the user to the profile preferences" do + get "/hot" + expect(response).not_to redirect_to("/u/#{user.username}/preferences/profile") + end + end + end + describe "invalid request params" do before do @old_logger = Rails.logger diff --git a/spec/services/user_updater_spec.rb b/spec/services/user_updater_spec.rb index 65c8a395147..7cddd4a6ef4 100644 --- a/spec/services/user_updater_spec.rb +++ b/spec/services/user_updater_spec.rb @@ -602,40 +602,61 @@ RSpec.describe UserUpdater do end end - it "logs the action" do - user = Fabricate(:user, name: "Billy Bob") + context "when updating the name" do + it "logs the action" do + user = Fabricate(:user, name: "Billy Bob") - expect do UserUpdater.new(user, user).update(name: "Jim Tom") end.to change { - UserHistory.count - }.by(1) + expect do UserUpdater.new(user, user).update(name: "Jim Tom") end.to change { + UserHistory.count + }.by(1) - expect(UserHistory.last.action).to eq(UserHistory.actions[:change_name]) + expect(UserHistory.last.action).to eq(UserHistory.actions[:change_name]) - expect do UserUpdater.new(user, user).update(name: "JiM TOm") end.to_not change { - UserHistory.count - } + expect do UserUpdater.new(user, user).update(name: "JiM TOm") end.to_not change { + UserHistory.count + } - expect do UserUpdater.new(user, user).update(bio_raw: "foo bar") end.to_not change { - UserHistory.count - } + expect do UserUpdater.new(user, user).update(bio_raw: "foo bar") end.to_not change { + UserHistory.count + } - user_without_name = Fabricate(:user, name: nil) + user_without_name = Fabricate(:user, name: nil) - expect do - UserUpdater.new(user_without_name, user_without_name).update(bio_raw: "foo bar") - end.to_not change { UserHistory.count } + expect do + UserUpdater.new(user_without_name, user_without_name).update(bio_raw: "foo bar") + end.to_not change { UserHistory.count } - expect do - UserUpdater.new(user_without_name, user_without_name).update(name: "Jim Tom") - end.to change { UserHistory.count }.by(1) + expect do + UserUpdater.new(user_without_name, user_without_name).update(name: "Jim Tom") + end.to change { UserHistory.count }.by(1) - expect(UserHistory.last.action).to eq(UserHistory.actions[:change_name]) + expect(UserHistory.last.action).to eq(UserHistory.actions[:change_name]) - expect do UserUpdater.new(user, user).update(name: "") end.to change { UserHistory.count }.by( - 1, - ) + expect do UserUpdater.new(user, user).update(name: "") end.to change { + UserHistory.count + }.by(1) - expect(UserHistory.last.action).to eq(UserHistory.actions[:change_name]) + expect(UserHistory.last.action).to eq(UserHistory.actions[:change_name]) + end + end + + context "when updating required fields" do + it "logs the action" do + user = Fabricate(:user) + Fabricate(:user_field, name: "favorite_pokemon", requirement: "for_all_users") + + UserRequiredFieldsVersion.create! + + expect do + UserUpdater.new(user, user).update(custom_fields: { "favorite_pokemon" => "Mudkip" }) + end.to change { UserHistory.count }.by(1) + + user.bump_required_fields_version + + expect do + UserUpdater.new(user, user).update(custom_fields: { "favorite_pokemon" => "Mudkip" }) + end.not_to change { UserHistory.count } + end end it "clears the homepage_id when the special 'custom' id is chosen" do diff --git a/spec/system/admin_user_fields_spec.rb b/spec/system/admin_user_fields_spec.rb index 1bcdb66f0f8..a3c2d5b8017 100644 --- a/spec/system/admin_user_fields_spec.rb +++ b/spec/system/admin_user_fields_spec.rb @@ -25,4 +25,38 @@ describe "Admin User Fields", type: :system, js: true do expect(user_fields_page).to have_text(/Description can't be blank/) end + + it "makes sure new required fields are editable after signup" do + user_fields_page.visit + + page.find(".user-fields .btn-primary").click + + form = page.find(".user-field") + editable_label = I18n.t("admin_js.admin.user_fields.editable.title") + + user_fields_page.choose_requirement("for_all_users") + + expect(form).to have_field(editable_label, checked: true, disabled: true) + + user_fields_page.choose_requirement("optional") + + expect(form).to have_field(editable_label, checked: false, disabled: false) + end + + it "requires confirmation when applying required fields retroactively" do + user_fields_page.visit + + page.find(".user-fields .btn-primary").click + + form = page.find(".user-field") + + form.find(".user-field-name").fill_in(with: "Favourite Pokémon") + form.find(".user-field-desc").fill_in(with: "Hint: It's Mudkip") + + user_fields_page.choose_requirement("for_all_users") + + form.find(".btn-primary").click + + expect(page).to have_text(I18n.t("admin_js.admin.user_fields.requirement.confirmation")) + end end diff --git a/spec/system/page_objects/pages/admin_user_fields.rb b/spec/system/page_objects/pages/admin_user_fields.rb index 3b69ac622fa..37692719c3d 100644 --- a/spec/system/page_objects/pages/admin_user_fields.rb +++ b/spec/system/page_objects/pages/admin_user_fields.rb @@ -8,6 +8,12 @@ module PageObjects self end + def choose_requirement(requirement) + form = page.find(".user-field") + + form.choose(I18n.t("admin_js.admin.user_fields.requirement.#{requirement}.title")) + end + def add_field(name: nil, description: nil, requirement: nil, preferences: []) page.find(".user-fields .btn-primary").click diff --git a/spec/system/page_objects/pages/user_preferences_profile.rb b/spec/system/page_objects/pages/user_preferences_profile.rb new file mode 100644 index 00000000000..20955819a76 --- /dev/null +++ b/spec/system/page_objects/pages/user_preferences_profile.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module PageObjects + module Pages + class UserPreferencesProfile < PageObjects::Pages::Base + def visit(user) + page.visit("/u/#{user.username}/preferences/profile") + self + end + end + end +end diff --git a/spec/system/user_page/user_preferences_profile_spec.rb b/spec/system/user_page/user_preferences_profile_spec.rb new file mode 100644 index 00000000000..f1ad6f8ba3d --- /dev/null +++ b/spec/system/user_page/user_preferences_profile_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +describe "User preferences | Profile", type: :system do + fab!(:user) { Fabricate(:user, active: true) } + let(:user_preferences_profile_page) { PageObjects::Pages::UserPreferencesProfile.new } + let(:user_preferences_page) { PageObjects::Pages::UserPreferences.new } + + before { sign_in(user) } + + describe "enforcing required fields" do + before do + UserRequiredFieldsVersion.create! + UserField.create!( + field_type: "text", + name: "Favourite Pokemon", + description: "Hint: It's Mudkip.", + requirement: :for_all_users, + editable: true, + ) + end + + it "redirects to the profile page to fill up required fields" do + visit("/") + + expect(page).to have_current_path("/u/bruce0/preferences/profile") + + expect(page).to have_selector( + ".alert-error", + text: I18n.t("js.user.preferences.profile.enforced_required_fields"), + ) + end + + it "disables client-side routing while missing required fields" do + user_preferences_profile_page.visit(user) + + find("#site-logo").click + + expect(page).to have_current_path("/u/bruce0/preferences/profile") + end + + it "allows user to fill up required fields" do + user_preferences_profile_page.visit(user) + + find(".user-field-favourite-pokemon input").fill_in(with: "Mudkip") + find(".save-button .btn-primary").click + + visit("/") + + expect(page).to have_current_path("/") + end + end +end