Automatically detect and apply OS theme on Login screen (#19928)

* Automatically detect and apply OS theme in Login screen

https://community.openproject.org/work_packages/66594

* add a migration to set the default theme of anonymous user to sync_with_os

* add a migration spec

* add a feature spec

* write migrations in raw sql

* update anonymous user pref before testing the theme applied

---------

Co-authored-by: Behrokh Satarnejad <62008897+bsatarnejad@users.noreply.github.com>
Co-authored-by: Behrokh Satarnejad <b.satarnejad@openproject.com>
This commit is contained in:
Kabiru Mwenja
2025-10-20 14:19:56 +03:00
committed by GitHub
parent 082dfabde4
commit 8be53a8c6c
3 changed files with 126 additions and 0 deletions
@@ -0,0 +1,25 @@
# frozen_string_literal: true
class SetAnonymousUserThemeToSyncWithOs < ActiveRecord::Migration[8.0]
def up
say "Set anonymous user theme to sync_with_os"
execute <<~SQL.squish
UPDATE user_preferences
SET settings = settings || '{"theme": "sync_with_os"}'::jsonb
WHERE user_id = (
SELECT id FROM users WHERE type = 'AnonymousUser' LIMIT 1
);
SQL
end
def down
say "Rollback: Reset anonymous user theme to light"
execute <<~SQL.squish
UPDATE user_preferences
SET settings = settings || '{"theme": "light"}'::jsonb
WHERE user_id = (
SELECT id FROM users WHERE type = 'AnonymousUser' LIMIT 1
);
SQL
end
end
@@ -0,0 +1,83 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
require Rails.root.join("db/migrate/20251017111720_set_anonymous_user_theme_to_sync_with_os.rb")
RSpec.describe SetAnonymousUserThemeToSyncWithOs, type: :model do
let(:anonymous_user) { User.anonymous }
before do
# Ensure the anonymous user has a preference entry
anonymous_user.pref.update(settings: { "theme" => "light" })
end
describe "up migration" do
it "sets the anonymous user theme to sync_with_os" do
expect(anonymous_user.pref.settings["theme"]).to eq("light")
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
anonymous_user.pref.reload
expect(anonymous_user.pref.settings["theme"]).to eq("sync_with_os")
end
it "does not affect other users" do
other_user = create(:user)
other_user.pref.update(settings: { "theme" => "dark" })
ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) }
other_user.pref.reload
expect(other_user.pref.settings["theme"]).to eq("dark")
end
end
describe "down migration" do
it "reverts the anonymous user theme back to light" do
anonymous_user.pref.update(settings: { "theme" => "sync_with_os" })
ActiveRecord::Migration.suppress_messages { described_class.migrate(:down) }
anonymous_user.pref.reload
expect(anonymous_user.pref.settings["theme"]).to eq("light")
end
it "does not modify other user preferences" do
other_user = create(:user)
other_user.pref.update(settings: { "theme" => "dark" })
ActiveRecord::Migration.suppress_messages { described_class.migrate(:down) }
other_user.pref.reload
expect(other_user.pref.settings["theme"]).to eq("dark")
end
end
end
+18
View File
@@ -51,4 +51,22 @@ RSpec.describe "account/login" do
expect(rendered).not_to include "Password"
end
end
context "if user is not logged in" do
before do
User.anonymous.pref.update(settings: { "theme" => "sync_with_os" })
end
it "uses the OS-synced theme preference by default" do
theme_data = view.user_theme_data_attributes
expect(theme_data[:auto_theme_switcher_theme_value]).to eq("sync_with_os")
# Check that contrast flags exist
expect(theme_data).to have_key(:auto_theme_switcher_force_light_contrast_value)
expect(theme_data).to have_key(:auto_theme_switcher_force_dark_contrast_value)
# Check logo classes
expect(theme_data[:auto_theme_switcher_desktop_light_high_contrast_logo_class]).to eq("op-logo--link_high_contrast")
expect(theme_data[:auto_theme_switcher_mobile_white_logo_class]).to eq("op-logo--icon_white")
end
end
end