2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-03 23:54:20 +08:00
discourse/spec/models/remote_theme_spec.rb
David Taylor 5ba28911ca
DEV: Only load specific plugin bundles during qunit test (#33678)
Previously, running qunit tests for a plugin would load the JS of all
installed plugins. This can be problematic because, depending on the
plugins installed in the current environment, there can be unexpected
interactions between the plugins.

This commit updates our qunit system to check the
`tests.requiredPlugins` list from the theme/plugin `about.json` file,
and only loads the listed plugins. This means that the environment is
much more consistent across CI and different development environments.

Alongside that change, this commit also:

- Starts storing the original `about.json` for themes in the database

- Improves qunit error handling when the test environment fails to boot
(e.g. there's a bad import in a plugin)

- Restores plugin CSS in theme qunit tests
2025-07-21 21:00:48 +01:00

765 lines
26 KiB
Ruby

# frozen_string_literal: true
RSpec.describe RemoteTheme do
before do
FastImage
.stubs(:size)
.with { |arg| arg.match(%r{/screenshots/light\.jpeg}) }
.returns([800, 600])
FastImage
.stubs(:size)
.with { |arg| arg.match(%r{/screenshots/dark\.jpeg}) }
.returns([1024, 768])
end
describe "#import_theme" do
def about_json(
love_color: "FAFAFA",
tertiary_low_color: "FFFFFF",
color_scheme_name: "Amazing",
about_url: "https://www.site.com/about",
screenshots: %w[screenshots/light.jpeg screenshots/dark.jpeg]
)
<<~JSON
{
"name": "awesome theme",
"about_url": "#{about_url}",
"license_url": "https://www.site.com/license",
"theme_version": "1.0",
"minimum_discourse_version": "1.0.0",
"assets": {
"font": "assets/font.woff2"
},
"color_schemes": {
"#{color_scheme_name}": {
"love": "#{love_color}",
"tertiary-low": "#{tertiary_low_color}"
}
},
"modifiers": {
"serialize_topic_excerpts": true,
"custom_homepage": {
"type": "setting",
"value": "boolean_setting"
},
"serialize_post_user_badges": {
"type": "setting",
"value": "list_setting"
}
},
"screenshots": #{screenshots.to_json}
}
JSON
end
let :scss_data do
"@font-face { font-family: magic; src: url($font)}; body {color: $color; content: $name;}"
end
let(:migration_js) { <<~JS }
export default function migrate(settings) {
return settings;
}
JS
let :initial_repo do
settings = <<~YAML
boolean_setting: true
list_setting:
type: list
default: ""
YAML
setup_git_repo(
"about.json" => about_json,
"desktop/desktop.scss" => scss_data,
"scss/oldpath.scss" => ".class2{color:blue}",
"stylesheets/file.scss" => ".class1{color:red}",
"stylesheets/empty.scss" => "",
"javascripts/discourse/controllers/test.js.es6" => "console.log('test');",
"test/acceptance/theme-test.js" => "assert.ok(true);",
"common/header.html" => "I AM HEADER",
"common/random.html" => "I AM SILLY",
"common/embedded.scss" => "EMBED",
"common/color_definitions.scss" => ":root{--color-var: red}",
"assets/font.woff2" => "FAKE FONT",
"settings.yaml" => settings,
"locales/en.yml" => "sometranslations",
"migrations/settings/0001-some-migration.js" => migration_js,
"screenshots/light.jpeg" => file_from_fixtures("logo.jpg", "images"),
"screenshots/dark.jpeg" => file_from_fixtures("logo.jpg", "images"),
)
end
let :initial_repo_url do
MockGitImporter.register("https://example.com/initial_repo.git", initial_repo)
end
after { `rm -fr #{initial_repo}` }
around(:each) { |group| MockGitImporter.with_mock { group.run } }
it "run pending theme settings migrations" do
add_to_git_repo(initial_repo, "migrations/settings/0002-another-migration.js" => <<~JS)
export default function migrate(settings) {
settings.set("boolean_setting", false);
return settings;
}
JS
theme = RemoteTheme.import_theme(initial_repo_url)
migrations = theme.theme_settings_migrations.order(:version)
expect(migrations.size).to eq(2)
first_migration = migrations[0]
second_migration = migrations[1]
expect(first_migration.version).to eq(1)
expect(second_migration.version).to eq(2)
expect(first_migration.name).to eq("some-migration")
expect(second_migration.name).to eq("another-migration")
expect(first_migration.diff).to eq("additions" => [], "deletions" => [])
expect(second_migration.diff).to eq(
"additions" => [{ "key" => "boolean_setting", "val" => false }],
"deletions" => [],
)
expect(theme.get_setting(:boolean_setting)).to eq(false)
expect(first_migration.theme_field.value).to eq(<<~JS)
export default function migrate(settings) {
return settings;
}
JS
expect(second_migration.theme_field.value).to eq(<<~JS)
export default function migrate(settings) {
settings.set("boolean_setting", false);
return settings;
}
JS
end
it "doesn't create theme if a migration fails" do
add_to_git_repo(initial_repo, "migrations/settings/0002-another-migration.js" => <<~JS)
export default function migrate(s) {
return null;
}
JS
expect do RemoteTheme.import_theme(initial_repo_url) end.to raise_error(
Theme::SettingsMigrationError,
).and not_change(Theme, :count).and not_change(RemoteTheme, :count)
end
it "doesn't partially update the theme when a migration fails" do
theme = RemoteTheme.import_theme(initial_repo_url)
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json(about_url: "https://updated.site.com"))
.tap { |h| h[:component] = true }
.to_json,
"stylesheets/file.scss" => ".class3 { color: green; }",
"common/header.html" => "I AM UPDATED HEADER",
"migrations/settings/0002-new-failing-migration.js" => <<~JS,
export default function migrate(settings) {
null.toString();
return settings;
}
JS
)
expect do theme.remote_theme.update_from_remote end.to raise_error(
Theme::SettingsMigrationError,
)
theme.reload
expect(theme.component).to eq(false)
expect(theme.remote_theme.about_url).to eq("https://www.site.com/about")
expect(theme.theme_fields.find_by(name: "header").value).to eq("I AM HEADER")
expect(
theme.theme_fields.find_by(type_id: ThemeField.types[:scss], name: "file").value,
).to eq(".class1{color:red}")
end
it "can correctly import a remote theme" do
time = Time.new("2000")
freeze_time time
theme = RemoteTheme.import_theme(initial_repo_url)
remote = theme.remote_theme
expect(theme.name).to eq("awesome theme")
expect(remote.remote_url).to eq(initial_repo_url)
expect(remote.remote_version).to eq(`cd #{initial_repo} && git rev-parse HEAD`.strip)
expect(remote.local_version).to eq(`cd #{initial_repo} && git rev-parse HEAD`.strip)
expect(remote.about_url).to eq("https://www.site.com/about")
expect(remote.license_url).to eq("https://www.site.com/license")
expect(remote.theme_version).to eq("1.0")
expect(remote.minimum_discourse_version).to eq("1.0.0")
expect(theme.theme_modifier_set.serialize_topic_excerpts).to eq(true)
expect(theme.theme_modifier_set.custom_homepage).to eq(true)
expect(theme.theme_fields.length).to eq(15)
mapped = Hash[*theme.theme_fields.map { |f| ["#{f.target_id}-#{f.name}", f.value] }.flatten]
expect(mapped["0-header"]).to eq("I AM HEADER")
expect(mapped["1-scss"]).to eq(scss_data)
expect(mapped["0-embedded_scss"]).to eq("EMBED")
expect(mapped["0-color_definitions"]).to eq(":root{--color-var: red}")
expect(mapped["0-font"]).to eq("")
expect(mapped["3-yaml"]).to eq(
"boolean_setting: true\nlist_setting:\n type: list\n default: \"\"\n",
)
expect(mapped["4-en"]).to eq("sometranslations")
expect(mapped["7-acceptance/theme-test.js"]).to eq("assert.ok(true);")
expect(mapped["8-0001-some-migration"]).to eq(
"export default function migrate(settings) {\n return settings;\n}\n",
)
expect(mapped.length).to eq(15)
expect(theme.settings.length).to eq(2)
expect(theme.settings[:boolean_setting].value).to eq(true)
expect(theme.settings[:list_setting].value).to eq("")
# lets change the setting to see modifier reflects
theme.update_setting(:boolean_setting, false)
theme.update_setting(:list_setting, "badge1|badge2")
theme.save!
theme.reload
expect(theme.theme_modifier_set.custom_homepage).to eq(false)
expect(theme.theme_modifier_set.serialize_post_user_badges).to eq(%w[badge1 badge2])
expect(remote.remote_updated_at).to eq_time(time)
scheme = ColorScheme.find_by(theme_id: theme.id)
expect(scheme.name).to eq("Amazing")
expect(scheme.colors.find_by(name: "love").hex).to eq("fafafa")
expect(scheme.colors.find_by(name: "tertiary-low").hex).to eq("ffffff")
expect(theme.color_scheme_id).to eq(scheme.id)
theme.update(color_scheme_id: nil)
File.write("#{initial_repo}/common/header.html", "I AM UPDATED")
File.write(
"#{initial_repo}/about.json",
about_json(love_color: "EAEAEA", about_url: "https://newsite.com/about"),
)
File.write("#{initial_repo}/settings.yml", "integer_setting: 32")
`cd #{initial_repo} && git add settings.yml`
File.delete("#{initial_repo}/settings.yaml")
File.delete("#{initial_repo}/stylesheets/file.scss")
`cd #{initial_repo} && git commit -am "update"`
time = Time.new("2001")
freeze_time time
remote.update_remote_version
expect(remote.commits_behind).to eq(1)
expect(remote.remote_version).to eq(`cd #{initial_repo} && git rev-parse HEAD`.strip)
remote.update_from_remote
theme.reload
scheme = ColorScheme.find_by(theme_id: theme.id)
expect(scheme.name).to eq("Amazing")
expect(scheme.colors.find_by(name: "love").hex).to eq("eaeaea")
expect(theme.color_scheme_id).to eq(nil) # Should only be set on first import
mapped = Hash[*theme.theme_fields.map { |f| ["#{f.target_id}-#{f.name}", f.value] }.flatten]
# Scss file was deleted
expect(mapped["5-file"]).to eq(nil)
expect(mapped["0-header"]).to eq("I AM UPDATED")
expect(mapped["1-scss"]).to eq(scss_data)
expect(theme.settings.length).to eq(1)
expect(theme.settings[:integer_setting].value).to eq(32)
expect(remote.remote_updated_at).to eq_time(time)
expect(remote.about_url).to eq("https://newsite.com/about")
# It should be able to remove old colors as well
File.write(
"#{initial_repo}/about.json",
about_json(love_color: "BABABA", tertiary_low_color: "", color_scheme_name: "Amazing 2"),
)
`cd #{initial_repo} && git commit -am "update"`
remote.update_from_remote
theme.reload
scheme_count = ColorScheme.where(theme_id: theme.id).count
expect(scheme_count).to eq(1)
scheme = ColorScheme.find_by(theme_id: theme.id)
expect(scheme.colors.find_by(name: "tertiary_low_color")).to eq(nil)
end
it "can update themes with overwritten history" do
theme = RemoteTheme.import_theme(initial_repo_url)
remote = theme.remote_theme
old_version = `cd #{initial_repo} && git rev-parse HEAD`.strip
expect(theme.name).to eq("awesome theme")
expect(remote.remote_url).to eq(initial_repo_url)
expect(remote.local_version).to eq(old_version)
expect(remote.remote_version).to eq(old_version)
`cd #{initial_repo} && git commit --amend -m "amended commit"`
new_version = `cd #{initial_repo} && git rev-parse HEAD`.strip
# make sure that the amended commit does not exist anymore
`cd #{initial_repo} && git reflog expire --all --expire=now`
`cd #{initial_repo} && git prune`
remote.update_remote_version
expect(remote.reload.local_version).to eq(old_version)
expect(remote.reload.remote_version).to eq(new_version)
expect(remote.reload.commits_behind).to eq(-1)
end
it "runs only new migrations when updating a theme" do
add_to_git_repo(initial_repo, "settings.yaml" => <<~YAML)
first_integer_setting: 1
second_integer_setting: 2
YAML
add_to_git_repo(initial_repo, "migrations/settings/0002-another-migration.js" => <<~JS)
export default function migrate(settings) {
settings.set("first_integer_setting", 101);
return settings;
}
JS
theme = RemoteTheme.import_theme(initial_repo_url)
expect(theme.get_setting(:first_integer_setting)).to eq(101)
expect(theme.get_setting(:second_integer_setting)).to eq(2)
theme.update_setting(:first_integer_setting, 110)
add_to_git_repo(initial_repo, "migrations/settings/0003-yet-another-migration.js" => <<~JS)
export default function migrate(settings) {
settings.set("second_integer_setting", 201);
return settings;
}
JS
theme.remote_theme.update_from_remote
theme.reload
expect(theme.get_setting(:first_integer_setting)).to eq(110)
expect(theme.get_setting(:second_integer_setting)).to eq(201)
end
it "fails if theme has too many files" do
stub_const(RemoteTheme, "MAX_THEME_FILE_COUNT", 1) do
expect { RemoteTheme.import_theme(initial_repo_url) }.to raise_error(
RemoteTheme::ImportError,
I18n.t("themes.import_error.too_many_files", count: 17, limit: 1),
)
end
end
it "fails if files are too large" do
stub_const(RemoteTheme, "MAX_ASSET_FILE_SIZE", 1.byte) do
expect { RemoteTheme.import_theme(initial_repo_url) }.to raise_error(
RemoteTheme::ImportError,
I18n.t(
"themes.import_error.asset_too_big",
filename: "about.json",
limit: ActiveSupport::NumberHelper.number_to_human_size(1),
),
)
end
end
it "fails if theme is too large" do
stub_const(RemoteTheme, "MAX_THEME_SIZE", 1.byte) do
expect { RemoteTheme.import_theme(initial_repo_url) }.to raise_error(
RemoteTheme::ImportError,
I18n.t(
"themes.import_error.theme_too_big",
limit: ActiveSupport::NumberHelper.number_to_human_size(1),
),
)
end
end
describe "screenshots" do
it "fails if any of the provided screenshots is not an accepted file type" do
stub_const(ThemeScreenshotsHandler, "THEME_SCREENSHOT_ALLOWED_FILE_TYPES", [".bmp"]) do
expect { RemoteTheme.import_theme(initial_repo_url) }.to raise_error(
RemoteTheme::ImportError,
I18n.t(
"themes.import_error.screenshot_invalid_type",
file_name: "light.jpeg",
accepted_formats: ".bmp",
),
)
end
end
it "fails if any of the provided screenshots is too big" do
stub_const(ThemeScreenshotsHandler, "MAX_THEME_SCREENSHOT_FILE_SIZE", 1.byte) do
expect { RemoteTheme.import_theme(initial_repo_url) }.to raise_error(
RemoteTheme::ImportError,
I18n.t(
"themes.import_error.screenshot_invalid_size",
file_name: "light.jpeg",
max_size: "1 Bytes",
),
)
end
end
it "fails if any of the provided screenshots has dimensions that are too big" do
FastImage
.expects(:size)
.with { |arg| arg.match(%r{/screenshots/light\.jpeg}) }
.returns([512, 512])
stub_const(ThemeScreenshotsHandler, "MAX_THEME_SCREENSHOT_DIMENSIONS", [1, 1]) do
expect { RemoteTheme.import_theme(initial_repo_url) }.to raise_error(
RemoteTheme::ImportError,
I18n.t(
"themes.import_error.screenshot_invalid_dimensions",
file_name: "light.jpeg",
width: 512,
height: 512,
max_width: 1,
max_height: 1,
),
)
end
end
it "creates uploads and associated theme fields for all theme screenshots" do
theme = RemoteTheme.import_theme(initial_repo_url)
screenshot_1 = theme.theme_fields.find_by(name: "screenshot_light")
screenshot_2 = theme.theme_fields.find_by(name: "screenshot_dark")
expect(screenshot_1).to be_present
expect(screenshot_1.type_id).to eq(ThemeField.types[:theme_screenshot_upload_var])
expect(screenshot_2).to be_present
expect(screenshot_2.type_id).to eq(ThemeField.types[:theme_screenshot_upload_var])
expect(screenshot_1.upload).to be_present
expect(screenshot_2.upload).to be_present
expect(UploadReference.exists?(target: screenshot_1)).to eq(true)
expect(UploadReference.exists?(target: screenshot_2)).to eq(true)
expect(screenshot_1.upload.original_filename).to eq("light.jpeg")
expect(screenshot_2.upload.original_filename).to eq("dark.jpeg")
end
end
describe "theme site settings" do
it "creates theme site settings defined in about.json" do
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "enable_welcome_banner" => false })
.to_json,
)
theme = RemoteTheme.import_theme(initial_repo_url)
expect(theme.theme_site_settings.count).to eq(1)
expect(theme.theme_site_settings.first.name).to eq("enable_welcome_banner")
expect(theme.theme_site_settings.first.value).to eq("f")
expect(theme.theme_site_settings.first.data_type).to eq(SiteSetting.types[:bool])
end
it "does not remove theme site settings that are no longer in about.json" do
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge(
"theme_site_settings" => {
"enable_welcome_banner" => false,
"search_experience" => "search_field",
},
)
.to_json,
)
theme = RemoteTheme.import_theme(initial_repo_url)
expect(theme.theme_site_settings.count).to eq(2)
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "enable_welcome_banner" => false })
.to_json,
)
theme.remote_theme.update_from_remote
theme.reload
expect(theme.theme_site_settings.count).to eq(2)
expect(theme.theme_site_settings.first.name).to eq("enable_welcome_banner")
expect(theme.theme_site_settings.first.value).to eq("f")
expect(theme.theme_site_settings.second.name).to eq("search_experience")
expect(theme.theme_site_settings.second.value).to eq("search_field")
end
it "ignores non-themeable site settings" do
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "min_admin_password_length" => 20 })
.to_json,
)
theme = nil
theme = RemoteTheme.import_theme(initial_repo_url)
expect(theme.theme_site_settings.count).to eq(0)
end
# TODO (martin) Hard to test this without a better example...we don't have any
# theme site settings that are an enum with > 2 values.
xit "does not override user modified theme site settings" do
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "search_experience" => "search_field" })
.to_json,
)
theme = RemoteTheme.import_theme(initial_repo_url)
expect(theme.theme_site_settings.first.value).to eq("search_field")
theme.theme_site_settings.first.update!(value: "search_icon")
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "search_experience" => "search_field" })
.to_json,
)
theme.remote_theme.update_from_remote
theme.reload
expect(theme.theme_site_settings.first.value).to eq("search_icon")
end
it "does not update the existing theme site setting" do
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "search_experience" => "search_field" })
.to_json,
)
theme = RemoteTheme.import_theme(initial_repo_url)
expect(theme.theme_site_settings.first.name).to eq("search_experience")
expect(theme.theme_site_settings.first.value).to eq("search_field")
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "search_experience" => "search_icon" })
.to_json,
)
theme.remote_theme.update_from_remote
theme.reload
expect(theme.theme_site_settings.first.value).to eq("search_field")
end
it "makes sure to refresh the site setting theme site setting cache so we have full data there for each theme" do
add_to_git_repo(
initial_repo,
"about.json" =>
JSON
.parse(about_json)
.merge("theme_site_settings" => { "enable_welcome_banner" => false })
.to_json,
)
theme = RemoteTheme.import_theme(initial_repo_url)
expect(SiteSetting.theme_site_settings[theme.id][:enable_welcome_banner]).to eq(false)
expect(SiteSetting.theme_site_settings[theme.id][:search_experience]).to eq("search_icon")
end
it "makes sure to set the theme site setting cache with defaults correctly when the theme has no theme_site_settings overrides defined" do
theme = RemoteTheme.import_theme(initial_repo_url)
expect(SiteSetting.theme_site_settings[theme.id][:enable_welcome_banner]).to eq(true)
expect(SiteSetting.theme_site_settings[theme.id][:search_experience]).to eq("search_icon")
end
end
end
let(:github_repo) do
RemoteTheme.create!(
remote_url: "https://github.com/org/testtheme.git",
local_version: "a2ec030e551fc8d8579790e1954876fe769fe40a",
remote_version: "21122230dbfed804067849393c3332083ddd0c07",
commits_behind: 2,
)
end
let(:gitlab_repo) do
RemoteTheme.create!(
remote_url: "https://gitlab.com/org/repo.git",
local_version: "a2ec030e551fc8d8579790e1954876fe769fe40a",
remote_version: "21122230dbfed804067849393c3332083ddd0c07",
commits_behind: 5,
)
end
describe "#github_diff_link" do
it "is blank for non-github repos" do
expect(gitlab_repo.github_diff_link).to be_blank
end
it "returns URL for comparing between local_version and remote_version" do
expect(github_repo.github_diff_link).to eq(
"https://github.com/org/testtheme/compare/#{github_repo.local_version}...#{github_repo.remote_version}",
)
end
it "is blank when theme is up-to-date" do
github_repo.update!(local_version: github_repo.remote_version, commits_behind: 0)
expect(github_repo.reload.github_diff_link).to be_blank
end
end
describe ".extract_theme_info" do
let(:importer) { mock }
let(:theme_info) do
{
"name" => "My Theme",
"about_url" => "https://example.com/about",
"license_url" => "https://example.com/license",
}
end
it "raises an error if about.json is too big" do
importer.stubs(:file_size).with("about.json").returns(100_000_000)
expect { RemoteTheme.extract_theme_info(importer) }.to raise_error(
RemoteTheme::ImportError,
I18n.t(
"themes.import_error.about_json_too_big",
limit:
ActiveSupport::NumberHelper.number_to_human_size((RemoteTheme::MAX_METADATA_FILE_SIZE)),
),
)
end
it "raises an error if about.json is invalid" do
importer.stubs(:file_size).with("about.json").returns(123)
importer.stubs(:[]).with("about.json").returns("{")
expect { RemoteTheme.extract_theme_info(importer) }.to raise_error(
RemoteTheme::ImportError,
I18n.t("themes.import_error.about_json"),
)
end
it "returns extracted theme info" do
importer.stubs(:file_size).with("about.json").returns(123)
importer.stubs(:[]).with("about.json").returns(theme_info.to_json)
expect(RemoteTheme.extract_theme_info(importer)).to eq(theme_info)
end
end
describe ".joined_remotes" do
it "finds records that are associated with themes" do
github_repo
gitlab_repo
expect(RemoteTheme.joined_remotes).to eq([])
Fabricate(:theme, remote_theme: github_repo)
expect(RemoteTheme.joined_remotes).to eq([github_repo])
Fabricate(:theme, remote_theme: gitlab_repo)
expect(RemoteTheme.joined_remotes).to contain_exactly(github_repo, gitlab_repo)
end
end
describe ".out_of_date_themes" do
let(:remote) { RemoteTheme.create!(remote_url: "https://github.com/org/testtheme") }
let!(:theme) { Fabricate(:theme, remote_theme: remote) }
it "finds out of date themes" do
remote.update!(local_version: "old version", remote_version: "new version", commits_behind: 2)
expect(described_class.out_of_date_themes).to eq([[theme.name, theme.id]])
remote.update!(local_version: "new version", commits_behind: 0)
expect(described_class.out_of_date_themes).to eq([])
end
it "ignores disabled out of date themes" do
remote.update!(local_version: "old version", remote_version: "new version", commits_behind: 2)
theme.update!(enabled: false)
expect(described_class.out_of_date_themes).to eq([])
end
end
describe ".unreachable_themes" do
let(:remote) do
RemoteTheme.create!(
remote_url: "https://github.com/org/testtheme",
last_error_text: "can't contact this repo :(",
)
end
let!(:theme) { Fabricate(:theme, remote_theme: remote) }
it "finds out of date themes" do
expect(described_class.unreachable_themes).to eq([[theme.name, theme.id]])
remote.update!(last_error_text: nil)
expect(described_class.unreachable_themes).to eq([])
end
end
describe ".import_theme_from_directory" do
let(:theme_dir) { "#{Rails.root}/spec/fixtures/themes/discourse-test-theme" }
it "imports a theme from a directory" do
theme = RemoteTheme.import_theme_from_directory(theme_dir)
expect(theme.name).to eq("Header Icons")
expect(theme.theme_fields.count).to eq(7)
end
end
end