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:
Osama Sayegh
2021-04-07 10:39:57 +03:00
committed by GitHub
parent c10df4b58d
commit a53d8d3e61
23 changed files with 401 additions and 165 deletions

View File

@ -276,7 +276,7 @@ HTML
def transpile(html)
f = ThemeField.create!(target_id: Theme.targets[:mobile], theme_id: 1, name: "after_header", value: html)
f.ensure_baked!
[f.value_baked, f.javascript_cache]
[f.value_baked, f.javascript_cache, f]
end
it "transpiles ES6 code" do
@ -286,10 +286,21 @@ HTML
</script>
HTML
baked, javascript_cache = transpile(html)
baked, javascript_cache, field = transpile(html)
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
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.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
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.value = 'bill'
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
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_field.javascript_cache.content).to eq(transpiled.strip)
end
it 'is empty when the settings are invalid' do