FEATURE: Introduce theme/component QUnit tests (#12517)
This commit allows themes and theme components to have QUnit tests. To add tests to your theme/component, create a top-level directory in your theme and name it `test`, and Discourse will save all the files in that directory (and its sub-directories) as "tests files" in the database. While tests files/directories are not required to be organized in a specific way, we recommend that you follow Discourse core's tests [structure](https://github.com/discourse/discourse/tree/master/app/assets/javascripts/discourse/tests). Writing theme tests should be identical to writing plugins or core tests; all the `import` statements and APIs that you see in core (or plugins) to define/setup tests should just work in themes. You do need a working Discourse install to run theme tests, and you have 2 ways to run theme tests: * In the browser at the `/qunit` route. `/qunit` will run tests of all active themes/components as well as core and plugins. The `/qunit` now accepts a `theme_name` or `theme_url` params that you can use to run tests of a specific theme/component like so: `/qunit?theme_name=<your_theme_name>`. * In the command line using the `themes:qunit` rake task. This take is meant to run tests of a single theme/component so you need to provide it with a theme name or URL like so: `bundle exec rake themes:qunit[name=<theme_name>]` or `bundle exec rake themes:qunit[url=<theme_url>]`. There are some refactors to internal code that's responsible for processing themes/components in Discourse, most notably: * `<script type="text/discourse-plugin">` tags are automatically converted to modules. * The `theme-settings` service is removed in favor of a simple `lib` file responsible for managing theme settings. This was done to allow us to register/lookup theme settings very early in our Ember app lifecycle and because there was no reason for it to be an Ember service. These refactors should 100% backward compatible and invisible to theme developers.
This commit is contained in:
parent
c10df4b58d
commit
a53d8d3e61
|
@ -1,6 +1,7 @@
|
||||||
import { helperContext, registerUnbound } from "discourse-common/lib/helpers";
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import deprecated from "discourse-common/lib/deprecated";
|
import deprecated from "discourse-common/lib/deprecated";
|
||||||
|
import { getSetting as getThemeSetting } from "discourse/lib/theme-settings-store";
|
||||||
|
|
||||||
registerUnbound("theme-i18n", (themeId, key, params) => {
|
registerUnbound("theme-i18n", (themeId, key, params) => {
|
||||||
return I18n.t(`theme_translations.${themeId}.${key}`, params);
|
return I18n.t(`theme_translations.${themeId}.${key}`, params);
|
||||||
|
@ -18,5 +19,6 @@ registerUnbound("theme-setting", (themeId, key, hash) => {
|
||||||
{ since: "v2.2.0.beta8", dropFrom: "v2.3.0" }
|
{ since: "v2.2.0.beta8", dropFrom: "v2.3.0" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return helperContext().themeSettings.getSetting(themeId, key);
|
|
||||||
|
return getThemeSetting(themeId, key);
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,6 @@ export function autoLoadModules(container, registry) {
|
||||||
|
|
||||||
let context = {
|
let context = {
|
||||||
siteSettings: container.lookup("site-settings:main"),
|
siteSettings: container.lookup("site-settings:main"),
|
||||||
themeSettings: container.lookup("service:theme-settings"),
|
|
||||||
keyValueStore: container.lookup("key-value-store:main"),
|
keyValueStore: container.lookup("key-value-store:main"),
|
||||||
capabilities: container.lookup("capabilities:main"),
|
capabilities: container.lookup("capabilities:main"),
|
||||||
currentUser: container.lookup("current-user:main"),
|
currentUser: container.lookup("current-user:main"),
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { get } from "@ember/object";
|
||||||
|
|
||||||
|
const originalSettings = {};
|
||||||
|
const settings = {};
|
||||||
|
|
||||||
|
export function registerSettings(
|
||||||
|
themeId,
|
||||||
|
settingsObject,
|
||||||
|
{ force = false } = {}
|
||||||
|
) {
|
||||||
|
if (settings[themeId] && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originalSettings[themeId] = Object.assign({}, settingsObject);
|
||||||
|
const s = {};
|
||||||
|
Object.keys(settingsObject).forEach((key) => {
|
||||||
|
Object.defineProperty(s, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
return settingsObject[key];
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
settingsObject[key] = newVal;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
settings[themeId] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSetting(themeId, settingKey) {
|
||||||
|
if (settings[themeId]) {
|
||||||
|
return get(settings[themeId], settingKey);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getObjectForTheme(themeId) {
|
||||||
|
return settings[themeId];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetSettings() {
|
||||||
|
Object.keys(originalSettings).forEach((themeId) => {
|
||||||
|
Object.keys(originalSettings[themeId]).forEach((key) => {
|
||||||
|
settings[themeId][key] = originalSettings[themeId][key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
import Service from "@ember/service";
|
|
||||||
import { get } from "@ember/object";
|
|
||||||
|
|
||||||
export default Service.extend({
|
|
||||||
settings: null,
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this._settings = {};
|
|
||||||
},
|
|
||||||
|
|
||||||
registerSettings(themeId, settingsObject) {
|
|
||||||
this._settings[themeId] = settingsObject;
|
|
||||||
},
|
|
||||||
|
|
||||||
getSetting(themeId, settingsKey) {
|
|
||||||
if (this._settings[themeId]) {
|
|
||||||
return get(this._settings[themeId], settingsKey);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
getObjectForTheme(themeId) {
|
|
||||||
return this._settings[themeId];
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -17,6 +17,7 @@ import { setupS3CDN, setupURL } from "discourse-common/lib/get-url";
|
||||||
import Application from "../app";
|
import Application from "../app";
|
||||||
import MessageBus from "message-bus-client";
|
import MessageBus from "message-bus-client";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
|
import { resetSettings as resetThemeSettings } from "discourse/lib/theme-settings-store";
|
||||||
import QUnit from "qunit";
|
import QUnit from "qunit";
|
||||||
import { ScrollingDOMMethods } from "discourse/mixins/scrolling";
|
import { ScrollingDOMMethods } from "discourse/mixins/scrolling";
|
||||||
import Session from "discourse/models/session";
|
import Session from "discourse/models/session";
|
||||||
|
@ -154,6 +155,7 @@ function setupTestsCommon(application, container, config) {
|
||||||
QUnit.testStart(function (ctx) {
|
QUnit.testStart(function (ctx) {
|
||||||
bootbox.$body = $("#ember-testing");
|
bootbox.$body = $("#ember-testing");
|
||||||
let settings = resetSettings();
|
let settings = resetSettings();
|
||||||
|
resetThemeSettings();
|
||||||
|
|
||||||
if (config) {
|
if (config) {
|
||||||
// Ember CLI testing environment
|
// Ember CLI testing environment
|
||||||
|
@ -251,6 +253,8 @@ function setupTestsCommon(application, container, config) {
|
||||||
let pluginPath = getUrlParameter("qunit_single_plugin")
|
let pluginPath = getUrlParameter("qunit_single_plugin")
|
||||||
? "/" + getUrlParameter("qunit_single_plugin") + "/"
|
? "/" + getUrlParameter("qunit_single_plugin") + "/"
|
||||||
: "/plugins/";
|
: "/plugins/";
|
||||||
|
let themeOnly = getUrlParameter("theme_name") || getUrlParameter("theme_url");
|
||||||
|
|
||||||
if (getUrlParameter("qunit_disable_auto_start") === "1") {
|
if (getUrlParameter("qunit_disable_auto_start") === "1") {
|
||||||
QUnit.config.autostart = false;
|
QUnit.config.autostart = false;
|
||||||
}
|
}
|
||||||
|
@ -259,8 +263,20 @@ function setupTestsCommon(application, container, config) {
|
||||||
let isTest = /\-test/.test(entry);
|
let isTest = /\-test/.test(entry);
|
||||||
let regex = new RegExp(pluginPath);
|
let regex = new RegExp(pluginPath);
|
||||||
let isPlugin = regex.test(entry);
|
let isPlugin = regex.test(entry);
|
||||||
|
let isTheme = /^discourse\/theme\-\d+\/.+/.test(entry);
|
||||||
|
|
||||||
if (isTest && (!skipCore || isPlugin)) {
|
if (!isTest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeOnly) {
|
||||||
|
if (isTheme) {
|
||||||
|
require(entry, null, null, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipCore || isPlugin) {
|
||||||
require(entry, null, null, true);
|
require(entry, null, null, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,13 +41,3 @@
|
||||||
//= require setup-tests
|
//= require setup-tests
|
||||||
//= require test-shims
|
//= require test-shims
|
||||||
//= require jquery.magnific-popup.min.js
|
//= require jquery.magnific-popup.min.js
|
||||||
|
|
||||||
document.write(
|
|
||||||
'<div id="ember-testing-container"><div id="ember-testing"></div></div>'
|
|
||||||
);
|
|
||||||
document.write(
|
|
||||||
"<style>#ember-testing-container { position: fixed; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; transform: translateZ(0)} #ember-testing { zoom: 50%; }</style>"
|
|
||||||
);
|
|
||||||
|
|
||||||
let setupTestsLegacy = require("discourse/tests/setup-tests").setupTestsLegacy;
|
|
||||||
setupTestsLegacy(window.Discourse);
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// discourse-skip-module
|
||||||
|
|
||||||
|
document.write(
|
||||||
|
'<div id="ember-testing-container"><div id="ember-testing"></div></div>'
|
||||||
|
);
|
||||||
|
document.write(
|
||||||
|
"<style>#ember-testing-container { position: fixed; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; transform: translateZ(0)} #ember-testing { zoom: 50%; }</style>"
|
||||||
|
);
|
||||||
|
|
||||||
|
let setupTestsLegacy = require("discourse/tests/setup-tests").setupTestsLegacy;
|
||||||
|
setupTestsLegacy(window.Discourse);
|
|
@ -1,11 +1,26 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class QunitController < ApplicationController
|
class QunitController < ApplicationController
|
||||||
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required
|
skip_before_action *%i{
|
||||||
|
check_xhr
|
||||||
|
preload_json
|
||||||
|
redirect_to_login_if_required
|
||||||
|
}
|
||||||
layout false
|
layout false
|
||||||
|
|
||||||
# only used in test / dev
|
# only used in test / dev
|
||||||
def index
|
def index
|
||||||
raise Discourse::InvalidAccess.new if Rails.env.production?
|
raise Discourse::InvalidAccess.new if Rails.env.production?
|
||||||
|
if (theme_name = params[:theme_name]).present?
|
||||||
|
theme = Theme.find_by(name: theme_name)
|
||||||
|
raise Discourse::NotFound if theme.blank?
|
||||||
|
elsif (theme_url = params[:theme_url]).present?
|
||||||
|
theme = RemoteTheme.find_by(remote_url: theme_url)
|
||||||
|
raise Discourse::NotFound if theme.blank?
|
||||||
|
end
|
||||||
|
if theme.present?
|
||||||
|
request.env[:resolved_theme_ids] = [theme.id]
|
||||||
|
request.env[:skip_theme_ids_transformation] = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ class ThemeJavascriptsController < ApplicationController
|
||||||
:preload_json,
|
:preload_json,
|
||||||
:redirect_to_login_if_required,
|
:redirect_to_login_if_required,
|
||||||
:verify_authenticity_token,
|
:verify_authenticity_token,
|
||||||
only: [:show]
|
only: [:show, :show_tests]
|
||||||
)
|
)
|
||||||
|
|
||||||
before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: [:show]
|
before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: [:show]
|
||||||
|
@ -34,6 +34,29 @@ class ThemeJavascriptsController < ApplicationController
|
||||||
send_file(cache_file, disposition: :inline)
|
send_file(cache_file, disposition: :inline)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_tests
|
||||||
|
raise Discourse::NotFound if Rails.env.production?
|
||||||
|
|
||||||
|
theme_id = params.require(:theme_id)
|
||||||
|
theme = Theme.find(theme_id)
|
||||||
|
content = ThemeField
|
||||||
|
.where(
|
||||||
|
theme_id: theme_id,
|
||||||
|
target_id: Theme.targets[:tests_js]
|
||||||
|
)
|
||||||
|
.each(&:ensure_baked!)
|
||||||
|
.map(&:value_baked)
|
||||||
|
.join("\n")
|
||||||
|
|
||||||
|
ThemeJavascriptCompiler.force_default_settings(content, theme)
|
||||||
|
|
||||||
|
response.headers["Content-Length"] = content.size.to_s
|
||||||
|
response.headers["Last-Modified"] = Time.zone.now.httpdate
|
||||||
|
immutable_for(1.second)
|
||||||
|
|
||||||
|
send_data content, filename: "js-tests-theme-#{theme_id}.js", disposition: :inline
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def query
|
def query
|
||||||
|
|
|
@ -453,15 +453,30 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def theme_lookup(name)
|
def theme_lookup(name)
|
||||||
Theme.lookup_field(theme_ids, mobile_view? ? :mobile : :desktop, name)
|
Theme.lookup_field(
|
||||||
|
theme_ids,
|
||||||
|
mobile_view? ? :mobile : :desktop,
|
||||||
|
name,
|
||||||
|
skip_transformation: request.env[:skip_theme_ids_transformation].present?
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def theme_translations_lookup
|
def theme_translations_lookup
|
||||||
Theme.lookup_field(theme_ids, :translations, I18n.locale)
|
Theme.lookup_field(
|
||||||
|
theme_ids,
|
||||||
|
:translations,
|
||||||
|
I18n.locale,
|
||||||
|
skip_transformation: request.env[:skip_theme_ids_transformation].present?
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def theme_js_lookup
|
def theme_js_lookup
|
||||||
Theme.lookup_field(theme_ids, :extra_js, nil)
|
Theme.lookup_field(
|
||||||
|
theme_ids,
|
||||||
|
:extra_js,
|
||||||
|
nil,
|
||||||
|
skip_transformation: request.env[:skip_theme_ids_transformation].present?
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def discourse_stylesheet_link_tag(name, opts = {})
|
def discourse_stylesheet_link_tag(name, opts = {})
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module QunitHelper
|
||||||
|
def theme_tests
|
||||||
|
theme_ids = request.env[:resolved_theme_ids]
|
||||||
|
return "" if theme_ids.blank?
|
||||||
|
|
||||||
|
skip_transformation = request.env[:skip_theme_ids_transformation]
|
||||||
|
query = ThemeField
|
||||||
|
.joins(:theme)
|
||||||
|
.where(
|
||||||
|
target_id: Theme.targets[:tests_js],
|
||||||
|
theme_id: skip_transformation ? theme_ids : Theme.transform_ids(theme_ids)
|
||||||
|
)
|
||||||
|
.pluck(:theme_id)
|
||||||
|
.uniq
|
||||||
|
.map do |theme_id|
|
||||||
|
src = "#{GlobalSetting.cdn_url}#{Discourse.base_path}/theme-javascripts/tests/#{theme_id}.js"
|
||||||
|
"<script src='#{src}'></script>"
|
||||||
|
end
|
||||||
|
.join("\n")
|
||||||
|
.html_safe
|
||||||
|
end
|
||||||
|
end
|
|
@ -128,7 +128,7 @@ class Theme < ActiveRecord::Base
|
||||||
SvgSprite.expire_cache
|
SvgSprite.expire_cache
|
||||||
end
|
end
|
||||||
|
|
||||||
BASE_COMPILER_VERSION = 17
|
BASE_COMPILER_VERSION = 45
|
||||||
def self.compiler_version
|
def self.compiler_version
|
||||||
get_set_cache "compiler_version" do
|
get_set_cache "compiler_version" do
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -262,11 +262,11 @@ class Theme < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.lookup_field(theme_ids, target, field)
|
def self.lookup_field(theme_ids, target, field, skip_transformation: false)
|
||||||
return if theme_ids.blank?
|
return if theme_ids.blank?
|
||||||
theme_ids = [theme_ids] unless Array === theme_ids
|
theme_ids = [theme_ids] unless Array === theme_ids
|
||||||
|
|
||||||
theme_ids = transform_ids(theme_ids)
|
theme_ids = transform_ids(theme_ids) if !skip_transformation
|
||||||
cache_key = "#{theme_ids.join(",")}:#{target}:#{field}:#{Theme.compiler_version}"
|
cache_key = "#{theme_ids.join(",")}:#{target}:#{field}:#{Theme.compiler_version}"
|
||||||
lookup = @cache[cache_key]
|
lookup = @cache[cache_key]
|
||||||
return lookup.html_safe if lookup
|
return lookup.html_safe if lookup
|
||||||
|
@ -297,7 +297,7 @@ class Theme < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.targets
|
def self.targets
|
||||||
@targets ||= Enum.new(common: 0, desktop: 1, mobile: 2, settings: 3, translations: 4, extra_scss: 5, extra_js: 6)
|
@targets ||= Enum.new(common: 0, desktop: 1, mobile: 2, settings: 3, translations: 4, extra_scss: 5, extra_js: 6, tests_js: 7)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.lookup_target(target_id)
|
def self.lookup_target(target_id)
|
||||||
|
|
|
@ -94,10 +94,38 @@ class ThemeField < ActiveRecord::Base
|
||||||
node.remove
|
node.remove
|
||||||
end
|
end
|
||||||
|
|
||||||
doc.css('script[type="text/discourse-plugin"]').each do |node|
|
doc.css('script[type="text/discourse-plugin"]').each_with_index do |node, index|
|
||||||
next unless node['version'].present?
|
version = node['version']
|
||||||
|
next if version.blank?
|
||||||
|
|
||||||
|
initializer_name = "theme-field" +
|
||||||
|
"-#{self.id}" +
|
||||||
|
"-#{Theme.targets[self.target_id]}" +
|
||||||
|
"-#{ThemeField.types[self.type_id]}" +
|
||||||
|
"-script-#{index + 1}"
|
||||||
begin
|
begin
|
||||||
js_compiler.append_plugin_script(node.inner_html, node['version'])
|
js = <<~JS
|
||||||
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
import { rescueThemeError } from "discourse/lib/utilities";
|
||||||
|
|
||||||
|
const __theme_name__ = #{self.theme.name.to_s.inspect};
|
||||||
|
export default {
|
||||||
|
name: #{initializer_name.inspect},
|
||||||
|
after: "inject-objects",
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
withPluginApi(#{version.inspect}, (api) => {
|
||||||
|
try {
|
||||||
|
#{node.inner_html}
|
||||||
|
} catch(err) {
|
||||||
|
rescueThemeError(__theme_name__, err, api);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
JS
|
||||||
|
|
||||||
|
js_compiler.append_module(js, "discourse/initializers/#{initializer_name}", include_variables: true)
|
||||||
rescue ThemeJavascriptCompiler::CompileError => ex
|
rescue ThemeJavascriptCompiler::CompileError => ex
|
||||||
errors << ex.message
|
errors << ex.message
|
||||||
end
|
end
|
||||||
|
@ -132,7 +160,7 @@ class ThemeField < ActiveRecord::Base
|
||||||
begin
|
begin
|
||||||
case extension
|
case extension
|
||||||
when "js.es6", "js"
|
when "js.es6", "js"
|
||||||
js_compiler.append_module(content, filename)
|
js_compiler.append_module(content, filename, include_variables: true)
|
||||||
when "hbs"
|
when "hbs"
|
||||||
js_compiler.append_ember_template(filename.sub("discourse/templates/", ""), content)
|
js_compiler.append_ember_template(filename.sub("discourse/templates/", ""), content)
|
||||||
when "hbr", "raw.hbs"
|
when "hbr", "raw.hbs"
|
||||||
|
@ -285,6 +313,10 @@ class ThemeField < ActiveRecord::Base
|
||||||
Theme.targets[self.target_id] == :extra_js
|
Theme.targets[self.target_id] == :extra_js
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def js_tests_field?
|
||||||
|
Theme.targets[self.target_id] == :tests_js
|
||||||
|
end
|
||||||
|
|
||||||
def basic_scss_field?
|
def basic_scss_field?
|
||||||
ThemeField.basic_targets.include?(Theme.targets[self.target_id].to_s) &&
|
ThemeField.basic_targets.include?(Theme.targets[self.target_id].to_s) &&
|
||||||
ThemeField.scss_fields.include?(self.name)
|
ThemeField.scss_fields.include?(self.name)
|
||||||
|
@ -315,7 +347,7 @@ class ThemeField < ActiveRecord::Base
|
||||||
self.error = nil unless self.error.present?
|
self.error = nil unless self.error.present?
|
||||||
self.compiler_version = Theme.compiler_version
|
self.compiler_version = Theme.compiler_version
|
||||||
DB.after_commit { CSP::Extension.clear_theme_extensions_cache! }
|
DB.after_commit { CSP::Extension.clear_theme_extensions_cache! }
|
||||||
elsif extra_js_field?
|
elsif extra_js_field? || js_tests_field?
|
||||||
self.value_baked, self.error = process_extra_js(self.value)
|
self.value_baked, self.error = process_extra_js(self.value)
|
||||||
self.error = nil unless self.error.present?
|
self.error = nil unless self.error.present?
|
||||||
self.compiler_version = Theme.compiler_version
|
self.compiler_version = Theme.compiler_version
|
||||||
|
@ -422,7 +454,7 @@ class ThemeField < ActiveRecord::Base
|
||||||
hash = {}
|
hash = {}
|
||||||
OPTIONS.each do |option|
|
OPTIONS.each do |option|
|
||||||
plural = :"#{option}s"
|
plural = :"#{option}s"
|
||||||
hash[option] = @allowed_values[plural][0] if @allowed_values[plural] && @allowed_values[plural].length == 1
|
hash[option] = @allowed_values[plural][0] if @allowed_values[plural]&.length == 1
|
||||||
hash[option] = match[option] if hash[option].nil?
|
hash[option] = match[option] if hash[option].nil?
|
||||||
end
|
end
|
||||||
hash
|
hash
|
||||||
|
@ -457,6 +489,9 @@ class ThemeField < ActiveRecord::Base
|
||||||
ThemeFileMatcher.new(regex: /^javascripts\/(?<name>.+)$/,
|
ThemeFileMatcher.new(regex: /^javascripts\/(?<name>.+)$/,
|
||||||
targets: :extra_js, names: nil, types: :js,
|
targets: :extra_js, names: nil, types: :js,
|
||||||
canonical: -> (h) { "javascripts/#{h[:name]}" }),
|
canonical: -> (h) { "javascripts/#{h[:name]}" }),
|
||||||
|
ThemeFileMatcher.new(regex: /^test\/(?<name>.+)$/,
|
||||||
|
targets: :tests_js, names: nil, types: :js,
|
||||||
|
canonical: -> (h) { "test/#{h[:name]}" }),
|
||||||
ThemeFileMatcher.new(regex: /^settings\.ya?ml$/,
|
ThemeFileMatcher.new(regex: /^settings\.ya?ml$/,
|
||||||
names: "yaml", types: :yaml, targets: :settings,
|
names: "yaml", types: :yaml, targets: :settings,
|
||||||
canonical: -> (h) { "settings.yml" }),
|
canonical: -> (h) { "settings.yml" }),
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
<%= discourse_color_scheme_stylesheets %>
|
<%= discourse_color_scheme_stylesheets %>
|
||||||
<%= stylesheet_link_tag "test_helper" %>
|
<%= stylesheet_link_tag "test_helper" %>
|
||||||
<%= javascript_include_tag "test_helper" %>
|
<%= javascript_include_tag "test_helper" %>
|
||||||
|
<%= theme_tests %>
|
||||||
|
<%= theme_translations_lookup %>
|
||||||
|
<%= theme_js_lookup %>
|
||||||
|
<%= theme_lookup("head_tag") %>
|
||||||
|
<%= javascript_include_tag "test_starter" %>
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<script src="<%= ExtraLocalesController.url('admin') %>"></script>
|
<script src="<%= ExtraLocalesController.url('admin') %>"></script>
|
||||||
<meta property="og:title" content="">
|
<meta property="og:title" content="">
|
||||||
|
|
|
@ -527,6 +527,7 @@ Discourse::Application.routes.draw do
|
||||||
get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ }
|
get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ }
|
||||||
get "color-scheme-stylesheet/:id(/:theme_id)" => "stylesheets#color_scheme", constraints: { format: :json }
|
get "color-scheme-stylesheet/:id(/:theme_id)" => "stylesheets#color_scheme", constraints: { format: :json }
|
||||||
get "theme-javascripts/:digest.js" => "theme_javascripts#show", constraints: { digest: /\h{40}/ }
|
get "theme-javascripts/:digest.js" => "theme_javascripts#show", constraints: { digest: /\h{40}/ }
|
||||||
|
get "theme-javascripts/tests/:theme_id.js" => "theme_javascripts#show_tests"
|
||||||
|
|
||||||
post "uploads/lookup-metadata" => "uploads#metadata"
|
post "uploads/lookup-metadata" => "uploads#metadata"
|
||||||
post "uploads" => "uploads#create"
|
post "uploads" => "uploads#create"
|
||||||
|
|
|
@ -61,7 +61,7 @@ task "qunit:test", [:timeout, :qunit_path] do |_, args|
|
||||||
cmd = "node #{test_path}/run-qunit.js http://localhost:#{port}#{qunit_path}"
|
cmd = "node #{test_path}/run-qunit.js http://localhost:#{port}#{qunit_path}"
|
||||||
options = { seed: (ENV["QUNIT_SEED"] || Random.new.seed), hidepassed: 1 }
|
options = { seed: (ENV["QUNIT_SEED"] || Random.new.seed), hidepassed: 1 }
|
||||||
|
|
||||||
%w{module filter qunit_skip_core qunit_single_plugin}.each do |arg|
|
%w{module filter qunit_skip_core qunit_single_plugin theme_name theme_url}.each do |arg|
|
||||||
options[arg] = ENV[arg.upcase] if ENV[arg.upcase].present?
|
options[arg] = ENV[arg.upcase] if ENV[arg.upcase].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -96,3 +96,24 @@ task "themes:audit" => :environment do
|
||||||
puts repo
|
puts repo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "Run QUnit tests of a theme/component"
|
||||||
|
task "themes:qunit", :theme_name_or_url do |t, args|
|
||||||
|
name_or_url = args[:theme_name_or_url]
|
||||||
|
if name_or_url.blank?
|
||||||
|
raise "A theme name or URL must be provided."
|
||||||
|
end
|
||||||
|
if name_or_url =~ /^(url|name)=(.+)/
|
||||||
|
cmd = "THEME_#{Regexp.last_match(1).upcase}=#{Regexp.last_match(2)} "
|
||||||
|
cmd += `which rake`.strip + " qunit:test"
|
||||||
|
sh cmd
|
||||||
|
else
|
||||||
|
raise <<~MSG
|
||||||
|
Cannot parse passed argument #{name_or_url.inspect}.
|
||||||
|
Usage:
|
||||||
|
`bundle exec rake themes:unit[url=<theme_url>]`
|
||||||
|
OR
|
||||||
|
`bundle exec rake themes:unit[name=<theme_name>]`
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -151,6 +151,18 @@ class ThemeJavascriptCompiler
|
||||||
class CompileError < StandardError
|
class CompileError < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.force_default_settings(content, theme)
|
||||||
|
settings_hash = {}
|
||||||
|
theme.settings.each do |setting|
|
||||||
|
settings_hash[setting.name] = setting.default
|
||||||
|
end
|
||||||
|
content.prepend <<~JS
|
||||||
|
(function() {
|
||||||
|
require("discourse/lib/theme-settings-store").registerSettings(#{theme.id}, #{settings_hash.to_json}, { force: true });
|
||||||
|
})();
|
||||||
|
JS
|
||||||
|
end
|
||||||
|
|
||||||
attr_accessor :content
|
attr_accessor :content
|
||||||
|
|
||||||
def initialize(theme_id, theme_name)
|
def initialize(theme_id, theme_name)
|
||||||
|
@ -162,10 +174,8 @@ class ThemeJavascriptCompiler
|
||||||
def prepend_settings(settings_hash)
|
def prepend_settings(settings_hash)
|
||||||
@content.prepend <<~JS
|
@content.prepend <<~JS
|
||||||
(function() {
|
(function() {
|
||||||
if ('Discourse' in window && Discourse.__container__) {
|
if ('require' in window) {
|
||||||
Discourse.__container__
|
require("discourse/lib/theme-settings-store").registerSettings(#{@theme_id}, #{settings_hash.to_json});
|
||||||
.lookup("service:theme-settings")
|
|
||||||
.registerSettings(#{@theme_id}, #{settings_hash.to_json});
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
JS
|
JS
|
||||||
|
@ -173,8 +183,10 @@ class ThemeJavascriptCompiler
|
||||||
|
|
||||||
# TODO Error handling for handlebars templates
|
# TODO Error handling for handlebars templates
|
||||||
def append_ember_template(name, hbs_template)
|
def append_ember_template(name, hbs_template)
|
||||||
|
name = "javascripts/#{name}" if !name.start_with?("javascripts/")
|
||||||
name = name.inspect
|
name = name.inspect
|
||||||
compiled = EmberTemplatePrecompiler.new(@theme_id).compile(hbs_template)
|
compiled = EmberTemplatePrecompiler.new(@theme_id).compile(hbs_template)
|
||||||
|
# the `'Ember' in window` check is needed for no_ember pages
|
||||||
content << <<~JS
|
content << <<~JS
|
||||||
(function() {
|
(function() {
|
||||||
if ('Ember' in window) {
|
if ('Ember' in window) {
|
||||||
|
@ -204,18 +216,19 @@ class ThemeJavascriptCompiler
|
||||||
raise CompileError.new e.instance_variable_get(:@error) # e.message contains the entire template, which could be very long
|
raise CompileError.new e.instance_variable_get(:@error) # e.message contains the entire template, which could be very long
|
||||||
end
|
end
|
||||||
|
|
||||||
def append_plugin_script(script, api_version)
|
|
||||||
@content << transpile(script, api_version)
|
|
||||||
end
|
|
||||||
|
|
||||||
def append_raw_script(script)
|
def append_raw_script(script)
|
||||||
@content << script + "\n"
|
@content << script + "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
def append_module(script, name, include_variables: true)
|
def append_module(script, name, include_variables: true)
|
||||||
script = "#{theme_variables}#{script}" if include_variables
|
name = "discourse/theme-#{@theme_id}/#{name.gsub(/^discourse\//, '')}"
|
||||||
|
script = "#{theme_settings}#{script}" if include_variables
|
||||||
transpiler = DiscourseJsProcessor::Transpiler.new
|
transpiler = DiscourseJsProcessor::Transpiler.new
|
||||||
@content << transpiler.perform(script, "", name)
|
@content << <<~JS
|
||||||
|
if ('define' in window) {
|
||||||
|
#{transpiler.perform(script, "", name).strip}
|
||||||
|
}
|
||||||
|
JS
|
||||||
rescue MiniRacer::RuntimeError => ex
|
rescue MiniRacer::RuntimeError => ex
|
||||||
raise CompileError.new ex.message
|
raise CompileError.new ex.message
|
||||||
end
|
end
|
||||||
|
@ -226,11 +239,9 @@ class ThemeJavascriptCompiler
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def theme_variables
|
def theme_settings
|
||||||
<<~JS
|
<<~JS
|
||||||
const __theme_name__ = "#{@theme_name.gsub('"', "\\\"")}";
|
const settings = require("discourse/lib/theme-settings-store")
|
||||||
const settings = Discourse.__container__
|
|
||||||
.lookup("service:theme-settings")
|
|
||||||
.getObjectForTheme(#{@theme_id});
|
.getObjectForTheme(#{@theme_id});
|
||||||
const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`;
|
const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`;
|
||||||
JS
|
JS
|
||||||
|
@ -241,7 +252,8 @@ class ThemeJavascriptCompiler
|
||||||
wrapped = <<~PLUGIN_API_JS
|
wrapped = <<~PLUGIN_API_JS
|
||||||
(function() {
|
(function() {
|
||||||
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
||||||
#{theme_variables}
|
const __theme_name__ = #{@theme_name.to_s.inspect};
|
||||||
|
#{theme_settings}
|
||||||
Discourse._registerPluginCode('#{version}', api => {
|
Discourse._registerPluginCode('#{version}', api => {
|
||||||
try {
|
try {
|
||||||
#{es6_source}
|
#{es6_source}
|
||||||
|
|
|
@ -40,6 +40,7 @@ describe RemoteTheme do
|
||||||
"stylesheets/file.scss" => ".class1{color:red}",
|
"stylesheets/file.scss" => ".class1{color:red}",
|
||||||
"stylesheets/empty.scss" => "",
|
"stylesheets/empty.scss" => "",
|
||||||
"javascripts/discourse/controllers/test.js.es6" => "console.log('test');",
|
"javascripts/discourse/controllers/test.js.es6" => "console.log('test');",
|
||||||
|
"test/acceptance/theme-test.js" => "assert.ok(true);",
|
||||||
"common/header.html" => "I AM HEADER",
|
"common/header.html" => "I AM HEADER",
|
||||||
"common/random.html" => "I AM SILLY",
|
"common/random.html" => "I AM SILLY",
|
||||||
"common/embedded.scss" => "EMBED",
|
"common/embedded.scss" => "EMBED",
|
||||||
|
@ -74,7 +75,7 @@ describe RemoteTheme do
|
||||||
|
|
||||||
expect(@theme.theme_modifier_set.serialize_topic_excerpts).to eq(true)
|
expect(@theme.theme_modifier_set.serialize_topic_excerpts).to eq(true)
|
||||||
|
|
||||||
expect(@theme.theme_fields.length).to eq(10)
|
expect(@theme.theme_fields.length).to eq(11)
|
||||||
|
|
||||||
mapped = Hash[*@theme.theme_fields.map { |f| ["#{f.target_id}-#{f.name}", f.value] }.flatten]
|
mapped = Hash[*@theme.theme_fields.map { |f| ["#{f.target_id}-#{f.name}", f.value] }.flatten]
|
||||||
|
|
||||||
|
@ -88,8 +89,9 @@ describe RemoteTheme do
|
||||||
expect(mapped["3-yaml"]).to eq("boolean_setting: true")
|
expect(mapped["3-yaml"]).to eq("boolean_setting: true")
|
||||||
|
|
||||||
expect(mapped["4-en"]).to eq("sometranslations")
|
expect(mapped["4-en"]).to eq("sometranslations")
|
||||||
|
expect(mapped["7-acceptance/theme-test.js"]).to eq("assert.ok(true);")
|
||||||
|
|
||||||
expect(mapped.length).to eq(10)
|
expect(mapped.length).to eq(11)
|
||||||
|
|
||||||
expect(@theme.settings.length).to eq(1)
|
expect(@theme.settings.length).to eq(1)
|
||||||
expect(@theme.settings.first.value).to eq(true)
|
expect(@theme.settings.first.value).to eq(true)
|
||||||
|
|
|
@ -192,34 +192,22 @@ HTML
|
||||||
unknown_field = theme.set_field(target: :extra_js, name: "discourse/controllers/discovery.blah", value: "this wont work")
|
unknown_field = theme.set_field(target: :extra_js, name: "discourse/controllers/discovery.blah", value: "this wont work")
|
||||||
theme.save!
|
theme.save!
|
||||||
|
|
||||||
expected_js = <<~JS
|
js_field.reload
|
||||||
define("discourse/controllers/discovery", ["discourse/lib/ajax"], function (_ajax) {
|
expect(js_field.value_baked).to include("if ('define' in window) {")
|
||||||
"use strict";
|
expect(js_field.value_baked).to include("define(\"discourse/theme-#{theme.id}/controllers/discovery\"")
|
||||||
|
expect(js_field.value_baked).to include("console.log('hello from .js.es6');")
|
||||||
|
|
||||||
var __theme_name__ = "#{theme.name}";
|
expect(hbs_field.reload.value_baked).to include('Ember.TEMPLATES["javascripts/discovery"]')
|
||||||
|
|
||||||
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
|
||||||
|
|
||||||
var themePrefix = function themePrefix(key) {
|
|
||||||
return "theme_translations.#{theme.id}.".concat(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('hello from .js.es6');
|
|
||||||
});
|
|
||||||
JS
|
|
||||||
expect(js_field.reload.value_baked).to eq(expected_js.strip)
|
|
||||||
|
|
||||||
expect(hbs_field.reload.value_baked).to include('Ember.TEMPLATES["discovery"]')
|
|
||||||
expect(raw_hbs_field.reload.value_baked).to include('addRawTemplate("discovery"')
|
expect(raw_hbs_field.reload.value_baked).to include('addRawTemplate("discovery"')
|
||||||
expect(hbr_field.reload.value_baked).to include('addRawTemplate("other_discovery"')
|
expect(hbr_field.reload.value_baked).to include('addRawTemplate("other_discovery"')
|
||||||
expect(unknown_field.reload.value_baked).to eq("")
|
expect(unknown_field.reload.value_baked).to eq("")
|
||||||
expect(unknown_field.reload.error).to eq(I18n.t("themes.compile_error.unrecognized_extension", extension: "blah"))
|
expect(unknown_field.reload.error).to eq(I18n.t("themes.compile_error.unrecognized_extension", extension: "blah"))
|
||||||
|
|
||||||
# All together
|
# All together
|
||||||
expect(theme.javascript_cache.content).to include('Ember.TEMPLATES["discovery"]')
|
expect(theme.javascript_cache.content).to include('Ember.TEMPLATES["javascripts/discovery"]')
|
||||||
expect(theme.javascript_cache.content).to include('addRawTemplate("discovery"')
|
expect(theme.javascript_cache.content).to include('addRawTemplate("discovery"')
|
||||||
expect(theme.javascript_cache.content).to include('define("discourse/controllers/discovery"')
|
expect(theme.javascript_cache.content).to include("define(\"discourse/theme-#{theme.id}/controllers/discovery\"")
|
||||||
expect(theme.javascript_cache.content).to include('define("discourse/controllers/discovery-2"')
|
expect(theme.javascript_cache.content).to include("define(\"discourse/theme-#{theme.id}/controllers/discovery-2\"")
|
||||||
expect(theme.javascript_cache.content).to include("var settings =")
|
expect(theme.javascript_cache.content).to include("var settings =")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -276,7 +276,7 @@ HTML
|
||||||
def transpile(html)
|
def transpile(html)
|
||||||
f = ThemeField.create!(target_id: Theme.targets[:mobile], theme_id: 1, name: "after_header", value: html)
|
f = ThemeField.create!(target_id: Theme.targets[:mobile], theme_id: 1, name: "after_header", value: html)
|
||||||
f.ensure_baked!
|
f.ensure_baked!
|
||||||
[f.value_baked, f.javascript_cache]
|
[f.value_baked, f.javascript_cache, f]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "transpiles ES6 code" do
|
it "transpiles ES6 code" do
|
||||||
|
@ -286,10 +286,21 @@ HTML
|
||||||
</script>
|
</script>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
baked, javascript_cache = transpile(html)
|
baked, javascript_cache, field = transpile(html)
|
||||||
expect(baked).to include(javascript_cache.url)
|
expect(baked).to include(javascript_cache.url)
|
||||||
expect(javascript_cache.content).to include('var x = 1;')
|
|
||||||
expect(javascript_cache.content).to include("_registerPluginCode('0.1'")
|
expect(javascript_cache.content).to include("if ('define' in window) {")
|
||||||
|
expect(javascript_cache.content).to include(
|
||||||
|
"define(\"discourse/theme-#{field.theme_id}/initializers/theme-field-#{field.id}-mobile-html-script-1\""
|
||||||
|
)
|
||||||
|
expect(javascript_cache.content).to include(
|
||||||
|
"settings = require(\"discourse/lib/theme-settings-store\").getObjectForTheme(#{field.theme_id});"
|
||||||
|
)
|
||||||
|
expect(javascript_cache.content).to include("name: \"theme-field-#{field.id}-mobile-html-script-1\",")
|
||||||
|
expect(javascript_cache.content).to include("after: \"inject-objects\",")
|
||||||
|
expect(javascript_cache.content).to include("(0, _pluginApi.withPluginApi)(\"0.1\", function (api) {")
|
||||||
|
expect(javascript_cache.content).to include("var x = 1;")
|
||||||
|
expect(javascript_cache.content).to include("(0, _utilities.rescueThemeError)(__theme_name__, err, api);")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "wraps constants calls in a readOnlyError function" do
|
it "wraps constants calls in a readOnlyError function" do
|
||||||
|
@ -369,83 +380,32 @@ HTML
|
||||||
theme_field = theme.set_field(target: :common, name: :after_header, value: '<script type="text/discourse-plugin" version="1.0">alert(settings.name); let a = ()=>{};</script>')
|
theme_field = theme.set_field(target: :common, name: :after_header, value: '<script type="text/discourse-plugin" version="1.0">alert(settings.name); let a = ()=>{};</script>')
|
||||||
theme.save!
|
theme.save!
|
||||||
|
|
||||||
transpiled = <<~HTML
|
|
||||||
(function() {
|
|
||||||
if ('Discourse' in window && Discourse.__container__) {
|
|
||||||
Discourse.__container__
|
|
||||||
.lookup("service:theme-settings")
|
|
||||||
.registerSettings(#{theme.id}, {"name":"bob"});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
(function () {
|
|
||||||
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
|
||||||
var __theme_name__ = "awesome theme\\\"";
|
|
||||||
|
|
||||||
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
|
||||||
|
|
||||||
var themePrefix = function themePrefix(key) {
|
|
||||||
return "theme_translations.#{theme.id}.".concat(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
Discourse._registerPluginCode('1.0', function (api) {
|
|
||||||
try {
|
|
||||||
alert(settings.name);
|
|
||||||
|
|
||||||
var a = function a() {};
|
|
||||||
} catch (err) {
|
|
||||||
var rescue = require("discourse/lib/utilities").rescueThemeError;
|
|
||||||
|
|
||||||
rescue(__theme_name__, err, api);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
HTML
|
|
||||||
|
|
||||||
theme_field.reload
|
theme_field.reload
|
||||||
expect(Theme.lookup_field(theme.id, :desktop, :after_header)).to include(theme_field.javascript_cache.url)
|
expect(Theme.lookup_field(theme.id, :desktop, :after_header)).to include(theme_field.javascript_cache.url)
|
||||||
expect(theme_field.javascript_cache.content).to eq(transpiled.strip)
|
expect(theme_field.javascript_cache.content).to include("if ('require' in window) {")
|
||||||
|
expect(theme_field.javascript_cache.content).to include(
|
||||||
|
"require(\"discourse/lib/theme-settings-store\").registerSettings(#{theme_field.theme.id}, {\"name\":\"bob\"});"
|
||||||
|
)
|
||||||
|
expect(theme_field.javascript_cache.content).to include("if ('define' in window) {")
|
||||||
|
expect(theme_field.javascript_cache.content).to include(
|
||||||
|
"define(\"discourse/theme-#{theme_field.theme.id}/initializers/theme-field-#{theme_field.id}-common-html-script-1\","
|
||||||
|
)
|
||||||
|
expect(theme_field.javascript_cache.content).to include("var __theme_name__ = \"awesome theme\\\"\";")
|
||||||
|
expect(theme_field.javascript_cache.content).to include("name: \"theme-field-#{theme_field.id}-common-html-script-1\",")
|
||||||
|
expect(theme_field.javascript_cache.content).to include("after: \"inject-objects\",")
|
||||||
|
expect(theme_field.javascript_cache.content).to include("(0, _pluginApi.withPluginApi)(\"1.0\", function (api)")
|
||||||
|
expect(theme_field.javascript_cache.content).to include("alert(settings.name)")
|
||||||
|
expect(theme_field.javascript_cache.content).to include("var a = function a() {}")
|
||||||
|
|
||||||
setting = theme.settings.find { |s| s.name == :name }
|
setting = theme.settings.find { |s| s.name == :name }
|
||||||
setting.value = 'bill'
|
setting.value = 'bill'
|
||||||
theme.save!
|
theme.save!
|
||||||
|
|
||||||
transpiled = <<~HTML
|
|
||||||
(function() {
|
|
||||||
if ('Discourse' in window && Discourse.__container__) {
|
|
||||||
Discourse.__container__
|
|
||||||
.lookup("service:theme-settings")
|
|
||||||
.registerSettings(#{theme.id}, {"name":"bill"});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
(function () {
|
|
||||||
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
|
||||||
var __theme_name__ = "awesome theme\\\"";
|
|
||||||
|
|
||||||
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
|
||||||
|
|
||||||
var themePrefix = function themePrefix(key) {
|
|
||||||
return "theme_translations.#{theme.id}.".concat(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
Discourse._registerPluginCode('1.0', function (api) {
|
|
||||||
try {
|
|
||||||
alert(settings.name);
|
|
||||||
|
|
||||||
var a = function a() {};
|
|
||||||
} catch (err) {
|
|
||||||
var rescue = require("discourse/lib/utilities").rescueThemeError;
|
|
||||||
|
|
||||||
rescue(__theme_name__, err, api);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
HTML
|
|
||||||
|
|
||||||
theme_field.reload
|
theme_field.reload
|
||||||
|
expect(theme_field.javascript_cache.content).to include(
|
||||||
|
"require(\"discourse/lib/theme-settings-store\").registerSettings(#{theme_field.theme.id}, {\"name\":\"bill\"});"
|
||||||
|
)
|
||||||
expect(Theme.lookup_field(theme.id, :desktop, :after_header)).to include(theme_field.javascript_cache.url)
|
expect(Theme.lookup_field(theme.id, :desktop, :after_header)).to include(theme_field.javascript_cache.url)
|
||||||
expect(theme_field.javascript_cache.content).to eq(transpiled.strip)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is empty when the settings are invalid' do
|
it 'is empty when the settings are invalid' do
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe QunitController do
|
||||||
|
let(:theme) { Fabricate(:theme, name: 'main-theme') }
|
||||||
|
let(:component) { Fabricate(:theme, component: true, name: 'enabled-component') }
|
||||||
|
let(:disabled_component) { Fabricate(:theme, component: true, enabled: false, name: 'disabled-component') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Theme.destroy_all
|
||||||
|
theme.set_default!
|
||||||
|
component.add_relative_theme!(:parent, theme)
|
||||||
|
disabled_component.add_relative_theme!(:parent, theme)
|
||||||
|
[theme, component, disabled_component].each do |t|
|
||||||
|
t.set_field(
|
||||||
|
target: :extra_js,
|
||||||
|
type: :js,
|
||||||
|
name: "discourse/initializers/my-#{t.id}-initializer.js",
|
||||||
|
value: "console.log(#{t.id});"
|
||||||
|
)
|
||||||
|
t.set_field(
|
||||||
|
target: :tests_js,
|
||||||
|
type: :js,
|
||||||
|
name: "acceptance/some-test-#{t.id}.js",
|
||||||
|
value: "assert.ok(#{t.id});"
|
||||||
|
)
|
||||||
|
t.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when no theme is specified" do
|
||||||
|
it "includes tests of enabled theme + components" do
|
||||||
|
get '/qunit'
|
||||||
|
js_urls = JavascriptCache.where(theme_id: [theme.id, component.id]).map(&:url)
|
||||||
|
expect(js_urls.size).to eq(2)
|
||||||
|
js_urls.each do |url|
|
||||||
|
expect(response.body).to include(url)
|
||||||
|
end
|
||||||
|
[theme, component].each do |t|
|
||||||
|
expect(response.body).to include("/theme-javascripts/tests/#{t.id}.js")
|
||||||
|
end
|
||||||
|
|
||||||
|
js_urls = JavascriptCache.where(theme_id: disabled_component).map(&:url)
|
||||||
|
expect(js_urls.size).to eq(1)
|
||||||
|
js_urls.each do |url|
|
||||||
|
expect(response.body).not_to include(url)
|
||||||
|
end
|
||||||
|
expect(response.body).not_to include("/theme-javascripts/tests/#{disabled_component.id}.js")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a theme is specified" do
|
||||||
|
it "includes tests of the specified theme only" do
|
||||||
|
[theme, disabled_component].each do |t|
|
||||||
|
get "/qunit?theme_name=#{t.name}"
|
||||||
|
js_urls = JavascriptCache.where(theme_id: t.id).map(&:url)
|
||||||
|
expect(js_urls.size).to eq(1)
|
||||||
|
js_urls.each do |url|
|
||||||
|
expect(response.body).to include(url)
|
||||||
|
end
|
||||||
|
expect(response.body).to include("/theme-javascripts/tests/#{t.id}.js")
|
||||||
|
|
||||||
|
excluded = Theme.pluck(:id) - [t.id]
|
||||||
|
js_urls = JavascriptCache.where(theme_id: excluded).map(&:url)
|
||||||
|
expect(js_urls.size).to eq(2)
|
||||||
|
js_urls.each do |url|
|
||||||
|
expect(response.body).not_to include(url)
|
||||||
|
end
|
||||||
|
excluded.each do |id|
|
||||||
|
expect(response.body).not_to include("/theme-javascripts/tests/#{id}.js")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -54,4 +54,24 @@ describe ThemeJavascriptsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#show_tests" do
|
||||||
|
context "theme settings" do
|
||||||
|
let(:component) { Fabricate(:theme, component: true, name: 'enabled-component') }
|
||||||
|
|
||||||
|
it "forces default values" do
|
||||||
|
ThemeField.create!(
|
||||||
|
theme: component,
|
||||||
|
target_id: Theme.targets[:settings],
|
||||||
|
name: "yaml",
|
||||||
|
value: "num_setting: 5"
|
||||||
|
)
|
||||||
|
component.reload
|
||||||
|
component.update_setting(:num_setting, 643)
|
||||||
|
|
||||||
|
get "/theme-javascripts/tests/#{component.id}.js"
|
||||||
|
expect(response.body).to include("require(\"discourse/lib/theme-settings-store\").registerSettings(#{component.id}, {\"num_setting\":5}, { force: true });")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue