2019-04-30 08:27:42 +08:00
# frozen_string_literal: true
2022-07-28 10:27:38 +08:00
RSpec . describe Theme do
2024-02-02 00:28:10 +08:00
fab! ( :user )
fab! ( :theme ) { Fabricate ( :theme , user : user ) }
let ( :guardian ) { Guardian . new ( user ) }
let ( :child ) { Fabricate ( :theme , user : user , component : true ) }
2017-04-12 22:52:52 +08:00
2022-10-19 01:20:10 +08:00
before { ThemeJavascriptCompiler . disable_terser! }
2024-02-02 00:28:10 +08:00
after do
Theme . clear_cache!
ThemeJavascriptCompiler . enable_terser!
2017-04-12 22:52:52 +08:00
end
2017-05-05 02:03:07 +08:00
it " can properly clean up color schemes " do
scheme = ColorScheme . create! ( theme_id : theme . id , name : " test " )
scheme2 = ColorScheme . create! ( theme_id : theme . id , name : " test2 " )
2018-08-08 12:46:34 +08:00
Fabricate ( :theme , color_scheme_id : scheme2 . id )
2017-05-05 02:03:07 +08:00
theme . destroy!
scheme2 . reload
expect ( scheme2 ) . not_to eq ( nil )
expect ( scheme2 . theme_id ) . to eq ( nil )
expect ( ColorScheme . find_by ( id : scheme . id ) ) . to eq ( nil )
end
2017-04-12 22:52:52 +08:00
it " can support child themes " do
2017-05-03 04:01:01 +08:00
child . set_field ( target : :common , name : " header " , value : " World " )
child . set_field ( target : :desktop , name : " header " , value : " Desktop " )
child . set_field ( target : :mobile , name : " header " , value : " Mobile " )
2017-04-12 22:52:52 +08:00
child . save!
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( child . id , :desktop , " header " ) ) . to eq ( " World \n Desktop " )
expect ( Theme . lookup_field ( child . id , " mobile " , :header ) ) . to eq ( " World \n Mobile " )
2017-04-12 22:52:52 +08:00
2017-05-03 04:01:01 +08:00
child . set_field ( target : :common , name : " header " , value : " Worldie " )
2017-04-12 22:52:52 +08:00
child . save!
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( child . id , :mobile , :header ) ) . to eq ( " Worldie \n Mobile " )
2017-04-12 22:52:52 +08:00
2018-08-08 12:46:34 +08:00
parent = Fabricate ( :theme , user : user )
2017-04-12 22:52:52 +08:00
2017-05-03 04:01:01 +08:00
parent . set_field ( target : :common , name : " header " , value : " Common Parent " )
parent . set_field ( target : :mobile , name : " header " , value : " Mobile Parent " )
2017-04-12 22:52:52 +08:00
parent . save!
2019-11-28 13:19:01 +08:00
parent . add_relative_theme! ( :child , child )
2017-04-12 22:52:52 +08:00
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( parent . id , :mobile , " header " ) ) . to eq (
" Common Parent \n Mobile Parent \n Worldie \n Mobile " ,
)
2017-04-12 22:52:52 +08:00
end
2019-11-28 13:19:01 +08:00
it " can support parent themes " do
child . add_relative_theme! ( :parent , theme )
expect ( child . parent_themes ) . to eq ( [ theme ] )
end
2019-01-25 22:19:01 +08:00
it " can automatically disable for mismatching version " do
theme . create_remote_theme! ( remote_url : " " , minimum_discourse_version : " 99.99.99 " )
2019-08-29 22:47:08 +08:00
theme . save!
2018-08-08 12:46:34 +08:00
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ ] )
2018-08-08 12:46:34 +08:00
end
2021-06-15 14:57:17 +08:00
it " # transform_ids works with nil values " do
2019-01-26 01:00:19 +08:00
# Used in safe mode
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( nil ) ) . to eq ( [ ] )
2019-01-26 01:00:19 +08:00
end
2019-07-03 16:18:11 +08:00
it " # transform_ids filters out disabled components " do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ theme . id , child . id ] )
2019-07-03 16:18:11 +08:00
child . update! ( enabled : false )
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ theme . id ] )
2019-07-03 16:18:11 +08:00
end
2018-08-08 12:46:34 +08:00
it " doesn't allow multi-level theme components " do
grandchild = Fabricate ( :theme , user : user )
grandparent = Fabricate ( :theme , user : user )
2017-04-12 22:52:52 +08:00
2019-11-28 13:19:01 +08:00
expect do child . add_relative_theme! ( :child , grandchild ) end . to raise_error (
2018-08-08 12:46:34 +08:00
Discourse :: InvalidParameters ,
I18n . t ( " themes.errors.no_multilevels_components " ) ,
)
2019-11-28 13:19:01 +08:00
expect do grandparent . add_relative_theme! ( :child , theme ) end . to raise_error (
2018-08-08 12:46:34 +08:00
Discourse :: InvalidParameters ,
I18n . t ( " themes.errors.no_multilevels_components " ) ,
)
end
2017-04-12 22:52:52 +08:00
2018-08-08 12:46:34 +08:00
it " doesn't allow a child to be user selectable " do
child . update ( user_selectable : true )
expect ( child . errors . full_messages ) . to contain_exactly (
I18n . t ( " themes.errors.component_no_user_selectable " ) ,
)
end
it " doesn't allow a child to be set as the default theme " do
expect do child . set_default! end . to raise_error (
Discourse :: InvalidParameters ,
I18n . t ( " themes.errors.component_no_default " ) ,
)
2017-04-12 22:52:52 +08:00
end
2018-08-24 09:30:00 +08:00
it " doesn't allow a component to have color scheme " do
scheme = ColorScheme . create! ( name : " test " )
child . update ( color_scheme : scheme )
expect ( child . errors . full_messages ) . to contain_exactly (
I18n . t ( " themes.errors.component_no_color_scheme " ) ,
)
end
2017-04-12 22:52:52 +08:00
it " should correct bad html in body_tag_baked and head_tag_baked " do
2017-05-03 04:01:01 +08:00
theme . set_field ( target : :common , name : " head_tag " , value : " <b>I am bold " )
2017-04-12 22:52:52 +08:00
theme . save!
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( theme . id , :desktop , " head_tag " ) ) . to eq ( " <b>I am bold</b> " )
2017-04-12 22:52:52 +08:00
end
it " should precompile fragments in body and head tags " do
with_template = <<HTML
< script type = 'text/x-handlebars' name = 'template' >
{ { hello } }
< / script>
< script type = 'text/x-handlebars' data - template - name = 'raw_template.raw' >
{ { hello } }
< / script>
HTML
2017-05-03 04:01:01 +08:00
theme . set_field ( target : :common , name : " header " , value : with_template )
2017-04-12 22:52:52 +08:00
theme . save!
2018-10-15 12:55:23 +08:00
field = theme . theme_fields . find_by ( target_id : Theme . targets [ :common ] , name : " header " )
2018-07-12 12:18:21 +08:00
baked = Theme . lookup_field ( theme . id , :mobile , " header " )
2017-04-12 22:52:52 +08:00
2018-10-15 12:55:23 +08:00
expect ( baked ) . to include ( field . javascript_cache . url )
2022-09-01 18:50:46 +08:00
expect ( field . javascript_cache . content ) . to include ( " @ember/template-factory " )
2018-10-15 12:55:23 +08:00
expect ( field . javascript_cache . content ) . to include ( " raw-handlebars " )
2017-04-12 22:52:52 +08:00
end
2019-02-13 00:14:43 +08:00
it " can destroy unbaked theme without errors " do
with_template = <<HTML
< script type = 'text/x-handlebars' name = 'template' >
{ { hello } }
< / script>
< script type = 'text/x-handlebars' data - template - name = 'raw_template.raw' >
{ { hello } }
< / script>
HTML
theme . set_field ( target : :common , name : " header " , value : with_template )
theme . save!
field = theme . theme_fields . find_by ( target_id : Theme . targets [ :common ] , name : " header " )
baked = Theme . lookup_field ( theme . id , :mobile , " header " )
ThemeField . where ( id : field . id ) . update_all ( compiler_version : 0 ) # update_all to avoid callbacks
field . reload . destroy!
end
2017-04-12 22:52:52 +08:00
it " should create body_tag_baked on demand if needed " do
2017-05-03 04:01:01 +08:00
theme . set_field ( target : :common , name : :body_tag , value : " <b>test " )
2017-04-12 22:52:52 +08:00
theme . save
ThemeField . update_all ( value_baked : nil )
2018-07-12 12:18:21 +08:00
expect ( Theme . lookup_field ( theme . id , :desktop , :body_tag ) ) . to match ( %r{ <b>test</b> } )
2017-04-12 22:52:52 +08:00
end
2018-08-24 09:30:00 +08:00
describe " # switch_to_component! " do
it " correctly converts a theme to component " do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2018-08-24 09:30:00 +08:00
scheme = ColorScheme . create! ( name : " test " )
theme . update! ( color_scheme_id : scheme . id , user_selectable : true )
theme . set_default!
theme . switch_to_component!
theme . reload
expect ( theme . component ) . to eq ( true )
expect ( theme . user_selectable ) . to eq ( false )
expect ( theme . default? ) . to eq ( false )
expect ( theme . color_scheme_id ) . to eq ( nil )
expect ( ChildTheme . where ( parent_theme : theme ) . exists? ) . to eq ( false )
end
end
describe " # switch_to_theme! " do
it " correctly converts a component to theme " do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
2018-08-24 09:30:00 +08:00
child . switch_to_theme!
theme . reload
child . reload
expect ( child . component ) . to eq ( false )
expect ( ChildTheme . where ( child_theme : child ) . exists? ) . to eq ( false )
end
end
2018-08-08 12:46:34 +08:00
describe " .transform_ids " do
2019-01-25 22:19:01 +08:00
let! ( :orphan1 ) { Fabricate ( :theme , component : true ) }
2018-08-24 09:30:00 +08:00
let! ( :child ) { Fabricate ( :theme , component : true ) }
let! ( :child2 ) { Fabricate ( :theme , component : true ) }
2019-01-25 22:19:01 +08:00
let! ( :orphan2 ) { Fabricate ( :theme , component : true ) }
let! ( :orphan3 ) { Fabricate ( :theme , component : true ) }
let! ( :orphan4 ) { Fabricate ( :theme , component : true ) }
2018-08-08 12:46:34 +08:00
2018-08-24 09:30:00 +08:00
before do
2019-11-28 13:19:01 +08:00
theme . add_relative_theme! ( :child , child )
theme . add_relative_theme! ( :child , child2 )
2018-08-24 09:30:00 +08:00
end
2019-01-25 22:19:01 +08:00
it " returns an empty array if no ids are passed " do
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( nil ) ) . to eq ( [ ] )
2019-01-25 22:19:01 +08:00
end
2018-08-24 09:30:00 +08:00
it " adds the child themes of the parent " do
sorted = [ child . id , child2 . id ] . sort
2021-06-15 14:57:17 +08:00
expect ( Theme . transform_ids ( theme . id ) ) . to eq ( [ theme . id , * sorted ] )
2018-08-08 12:46:34 +08:00
end
end
2022-07-28 00:14:14 +08:00
describe " plugin api " do
2017-04-12 22:52:52 +08:00
def transpile ( html )
2018-03-05 08:04:23 +08:00
f =
ThemeField . create! (
target_id : Theme . targets [ :mobile ] ,
theme_id : 1 ,
name : " after_header " ,
value : html ,
)
2019-04-12 18:36:08 +08:00
f . ensure_baked!
2021-04-12 20:02:58 +08:00
[ f . value_baked , f . javascript_cache , f ]
2017-04-12 22:52:52 +08:00
end
it " transpiles ES6 code " do
html = <<HTML
< script type = 'text/discourse-plugin' version = '0.1' >
const x = 1 ;
< / script>
HTML
2021-04-12 20:02:58 +08:00
baked , javascript_cache , field = transpile ( html )
2018-10-15 12:55:23 +08:00
expect ( baked ) . to include ( javascript_cache . url )
2021-04-12 20:02:58 +08:00
expect ( javascript_cache . content ) . to include ( " if ('define' in window) { " )
expect ( javascript_cache . content ) . to include (
2023-08-19 01:15:23 +08:00
" define( \" discourse/theme- #{ field . theme_id } /discourse/initializers/theme-field- #{ field . id } -mobile-html-script-1 \" " ,
2021-04-12 20:02:58 +08:00
)
expect ( javascript_cache . content ) . to include (
" settings = require( \" discourse/lib/theme-settings-store \" ).getObjectForTheme( #{ field . theme_id } ); " ,
2023-01-09 19:18:21 +08:00
)
2021-04-12 20:02:58 +08:00
expect ( javascript_cache . content ) . to include (
" name: \" theme-field- #{ field . id } -mobile-html-script-1 \" , " ,
)
expect ( javascript_cache . content ) . to include ( " after: \" inject-objects \" , " )
2022-08-09 18:53:24 +08:00
expect ( javascript_cache . content ) . to include ( " (0, _pluginApi.withPluginApi)( \" 0.1 \" , api => " )
expect ( javascript_cache . content ) . to include ( " const x = 1; " )
2017-04-12 22:52:52 +08:00
end
end
2022-07-28 00:14:14 +08:00
describe " theme upload vars " do
2017-05-08 23:38:48 +08:00
let :image do
file_from_fixtures ( " logo.png " )
end
it " can handle uploads based of ThemeField " do
2017-05-11 06:16:57 +08:00
upload = UploadCreator . new ( image , " logo.png " ) . create_for ( - 1 )
2017-05-08 23:38:48 +08:00
theme . set_field ( target : :common , name : :logo , upload_id : upload . id , type : :theme_upload_var )
theme . set_field ( target : :common , name : :scss , value : " body {background-image: url($logo)} " )
theme . save!
# make sure we do not nuke it
freeze_time ( SiteSetting . clean_orphan_uploads_grace_period_hours + 1 ) . hours . from_now
Jobs :: CleanUpUploads . new . execute ( nil )
2022-07-28 10:27:38 +08:00
expect ( Upload . where ( id : upload . id ) ) . to be_exists
2017-05-08 23:38:48 +08:00
2017-05-10 05:20:28 +08:00
# no error for theme field
theme . reload
expect ( theme . theme_fields . find_by ( name : :scss ) . error ) . to eq ( nil )
2017-05-08 23:38:48 +08:00
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : theme . id )
scss , _map =
Stylesheet :: Manager :: Builder . new (
target : :desktop_theme ,
theme : theme ,
manager : manager ,
) . compile ( force : true )
2017-05-08 23:38:48 +08:00
expect ( scss ) . to include ( upload . url )
end
2017-05-03 04:01:01 +08:00
end
2022-07-28 00:14:14 +08:00
describe " theme settings " do
2018-03-05 12:35:41 +08:00
it " allows values to be used in scss " do
2018-03-05 08:04:23 +08:00
theme . set_field (
target : :settings ,
name : :yaml ,
value : " background_color: red \n font_size: 25px " ,
)
theme . set_field (
target : :common ,
name : :scss ,
value : " body {background-color: $background_color; font-size: $font-size} " ,
)
theme . save!
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : theme . id )
scss , _map =
Stylesheet :: Manager :: Builder . new (
target : :desktop_theme ,
theme : theme ,
manager : manager ,
) . compile ( force : true )
2018-03-05 08:04:23 +08:00
expect ( scss ) . to include ( " background-color:red " )
expect ( scss ) . to include ( " font-size:25px " )
2018-03-05 12:35:41 +08:00
2024-02-01 10:26:56 +08:00
setting = theme . settings [ :font_size ]
2018-03-05 12:35:41 +08:00
setting . value = " 30px "
2019-04-12 18:36:08 +08:00
theme . save!
2018-03-05 12:35:41 +08:00
2021-06-15 14:57:17 +08:00
scss , _map =
Stylesheet :: Manager :: Builder . new (
target : :desktop_theme ,
theme : theme ,
manager : manager ,
) . compile ( force : true )
2018-03-05 12:35:41 +08:00
expect ( scss ) . to include ( " font-size:30px " )
2019-03-08 16:58:06 +08:00
# Escapes correctly. If not, compiling this would throw an exception
DEV: Introduce `run_theme_migration` spec helper in test environment (#26845)
This commit introduces the `run_theme_migration` spec helper to allow
theme developers to write RSpec tests for theme migrations. For example,
this allows the following RSpec test to be written in themes:
```
RSpec.describe "0003-migrate-small-links-setting migration" do
let!(:theme) { upload_theme_component }
it "should set target property to `_blank` if previous target component is not valid or empty" do
theme.theme_settings.create!(
name: "small_links",
theme: theme,
data_type: ThemeSetting.types[:string],
value: "some text, #|some text 2, #, invalid target",
)
run_theme_migration(theme, "0003-migrate-small-links-setting")
expect(theme.settings[:small_links].value).to eq(
[
{ "text" => "some text", "url" => "#", "target" => "_blank" },
{ "text" => "some text 2", "url" => "#", "target" => "_blank" },
],
)
end
end
```
This change is being introduced because we realised that writting just
javascript tests for the migrations is insufficient since javascript
tests do not ensure that the migrated theme settings can actually be
successfully saved into the database. Hence, we are introduce this
helper as a way for theme developers to write "end-to-end" migrations
tests.
2024-05-03 06:29:18 +08:00
setting . value = << ~ SCSS
2019-03-08 16:58:06 +08:00
\ #{$fakeinterpolatedvariable}
andanothervalue 'withquotes' ; margin : 0 ;
DEV: Introduce `run_theme_migration` spec helper in test environment (#26845)
This commit introduces the `run_theme_migration` spec helper to allow
theme developers to write RSpec tests for theme migrations. For example,
this allows the following RSpec test to be written in themes:
```
RSpec.describe "0003-migrate-small-links-setting migration" do
let!(:theme) { upload_theme_component }
it "should set target property to `_blank` if previous target component is not valid or empty" do
theme.theme_settings.create!(
name: "small_links",
theme: theme,
data_type: ThemeSetting.types[:string],
value: "some text, #|some text 2, #, invalid target",
)
run_theme_migration(theme, "0003-migrate-small-links-setting")
expect(theme.settings[:small_links].value).to eq(
[
{ "text" => "some text", "url" => "#", "target" => "_blank" },
{ "text" => "some text 2", "url" => "#", "target" => "_blank" },
],
)
end
end
```
This change is being introduced because we realised that writting just
javascript tests for the migrations is insufficient since javascript
tests do not ensure that the migrated theme settings can actually be
successfully saved into the database. Hence, we are introduce this
helper as a way for theme developers to write "end-to-end" migrations
tests.
2024-05-03 06:29:18 +08:00
SCSS
2019-03-08 16:58:06 +08:00
theme . set_field ( target : :common , name : :scss , value : " body {font-size: quote($font-size)} " )
theme . save!
2021-06-15 14:57:17 +08:00
scss , _map =
Stylesheet :: Manager :: Builder . new (
target : :desktop_theme ,
theme : theme ,
manager : manager ,
) . compile ( force : true )
2019-03-08 16:58:06 +08:00
expect ( scss ) . to include (
'font-size:"#{$fakeinterpolatedvariable}\a andanothervalue \'withquotes\'; margin: 0;\a"' ,
)
2018-03-05 08:04:23 +08:00
end
2018-03-05 12:35:41 +08:00
2022-02-19 06:55:47 +08:00
it " can use a setting straight away after introducing it " do
theme . set_field ( target : :common , name : :scss , value : " body {background-color: red;} " )
theme . save!
theme . reload
theme . set_field (
target : :settings ,
name : :yaml ,
value : " background_color: red \n font_size: 25px " ,
)
theme . set_field (
target : :common ,
name : :scss ,
value : " body {background-color: $background_color;} " ,
)
theme . save!
expect (
theme . theme_fields . find_by ( target_id : Theme . targets [ :common ] , name : " scss " ) . error ,
) . to eq ( nil )
end
2018-03-05 12:35:41 +08:00
it " allows values to be used in JS " do
2019-05-24 22:25:55 +08:00
theme . name = 'awesome theme"'
2018-03-05 12:35:41 +08:00
theme . set_field ( target : :settings , name : :yaml , value : " name: bob " )
2018-10-15 12:55:23 +08:00
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>' ,
)
2018-03-05 12:35:41 +08:00
theme . save!
2018-10-15 12:55:23 +08:00
theme_field . reload
expect ( Theme . lookup_field ( theme . id , :desktop , :after_header ) ) . to include (
theme_field . javascript_cache . url ,
)
2021-04-12 20:02:58 +08:00
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 (
2023-08-19 01:15:23 +08:00
" define( \" discourse/theme- #{ theme_field . theme . id } /discourse/initializers/theme-field- #{ theme_field . id } -common-html-script-1 \" , " ,
2023-01-09 19:18:21 +08:00
)
2021-04-12 20:02:58 +08:00
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 \" , " )
2022-08-09 18:53:24 +08:00
expect ( theme_field . javascript_cache . content ) . to include (
" (0, _pluginApi.withPluginApi)( \" 1.0 \" , api => " ,
)
2021-04-12 20:02:58 +08:00
expect ( theme_field . javascript_cache . content ) . to include ( " alert(settings.name) " )
2022-08-09 18:53:24 +08:00
expect ( theme_field . javascript_cache . content ) . to include ( " let a = () => {} " )
2018-03-05 12:35:41 +08:00
2024-02-01 10:26:56 +08:00
setting = theme . settings [ :name ]
2018-03-05 12:35:41 +08:00
setting . value = " bill "
2019-04-12 18:36:08 +08:00
theme . save!
2018-03-05 12:35:41 +08:00
2018-10-15 12:55:23 +08:00
theme_field . reload
2021-04-12 20:02:58 +08:00
expect ( theme_field . javascript_cache . content ) . to include (
" require( \" discourse/lib/theme-settings-store \" ).registerSettings( #{ theme_field . theme . id } , { \" name \" : \" bill \" }); " ,
2023-01-09 19:18:21 +08:00
)
2018-10-15 12:55:23 +08:00
expect ( Theme . lookup_field ( theme . id , :desktop , :after_header ) ) . to include (
theme_field . javascript_cache . url ,
2021-04-12 20:02:58 +08:00
)
2018-03-05 12:35:41 +08:00
end
2018-12-19 23:36:31 +08:00
it " is empty when the settings are invalid " do
theme . set_field ( target : :settings , name : :yaml , value : " nil_setting: " )
theme . save!
expect ( theme . settings ) . to be_empty
end
2018-03-05 08:04:23 +08:00
end
2018-07-12 12:18:21 +08:00
it " correctly caches theme ids " do
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
Theme . where . not ( id : theme . id ) . destroy_all
2017-05-03 23:31:16 +08:00
2018-08-08 12:46:34 +08:00
theme2 = Fabricate ( :theme )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
expect ( Theme . theme_ids ) . to contain_exactly ( theme . id , theme2 . id )
expect ( Theme . user_theme_ids ) . to eq ( [ ] )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
theme . update! ( user_selectable : true )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
expect ( Theme . user_theme_ids ) . to contain_exactly ( theme . id )
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
theme2 . update! ( user_selectable : true )
expect ( Theme . user_theme_ids ) . to contain_exactly ( theme . id , theme2 . id )
theme . update! ( user_selectable : false )
theme2 . update! ( user_selectable : false )
2017-04-15 01:35:12 +08:00
theme . set_default!
2018-08-08 12:46:34 +08:00
expect ( Theme . user_theme_ids ) . to contain_exactly ( theme . id )
2017-04-15 01:35:12 +08:00
theme . destroy
2018-08-08 12:46:34 +08:00
theme2 . destroy
2017-04-15 01:35:12 +08:00
2018-08-08 12:46:34 +08:00
expect ( Theme . theme_ids ) . to eq ( [ ] )
expect ( Theme . user_theme_ids ) . to eq ( [ ] )
2017-04-15 01:35:12 +08:00
end
2024-04-10 03:54:44 +08:00
it " correctly caches enabled_theme_and_component_ids " do
Theme . destroy_all
theme2 = Fabricate ( :theme )
expect ( Theme . enabled_theme_and_component_ids ) . to eq ( [ ] )
theme2 . update! ( user_selectable : true )
expect ( Theme . enabled_theme_and_component_ids ) . to contain_exactly ( theme2 . id )
theme2 . update! ( user_selectable : false )
theme2 . set_default!
expect ( Theme . enabled_theme_and_component_ids ) . to contain_exactly ( theme2 . id )
child2 = Fabricate ( :theme , component : true )
theme2 . add_relative_theme! ( :child , child2 )
expect ( Theme . enabled_theme_and_component_ids ) . to contain_exactly ( theme2 . id , child2 . id )
child2 . update! ( enabled : false )
expect ( Theme . enabled_theme_and_component_ids ) . to contain_exactly ( theme2 . id )
theme3 = Fabricate ( :theme , user_selectable : true )
child2 . update! ( enabled : true )
expect ( Theme . enabled_theme_and_component_ids ) . to contain_exactly (
theme2 . id ,
child2 . id ,
theme3 . id ,
)
theme3 . update! ( enabled : false )
expect ( Theme . enabled_theme_and_component_ids ) . to contain_exactly ( theme2 . id , child2 . id )
theme2 . destroy
theme3 . destroy
expect ( Theme . enabled_theme_and_component_ids ) . to eq ( [ ] )
end
2017-09-04 19:27:58 +08:00
it " correctly caches user_themes template " do
Theme . destroy_all
json = Site . json_for ( guardian )
user_themes = JSON . parse ( json ) [ " user_themes " ]
expect ( user_themes ) . to eq ( [ ] )
2018-08-08 12:46:34 +08:00
theme = Fabricate ( :theme , name : " bob " , user_selectable : true )
2019-04-12 18:36:08 +08:00
theme . save!
2017-09-04 19:27:58 +08:00
json = Site . json_for ( guardian )
user_themes = JSON . parse ( json ) [ " user_themes " ] . map { | t | t [ " name " ] }
expect ( user_themes ) . to eq ( [ " bob " ] )
theme . name = " sam "
theme . save!
json = Site . json_for ( guardian )
user_themes = JSON . parse ( json ) [ " user_themes " ] . map { | t | t [ " name " ] }
expect ( user_themes ) . to eq ( [ " sam " ] )
Theme . destroy_all
json = Site . json_for ( guardian )
user_themes = JSON . parse ( json ) [ " user_themes " ]
expect ( user_themes ) . to eq ( [ ] )
end
2018-07-12 12:18:21 +08:00
def cached_settings ( id )
2022-02-19 06:55:47 +08:00
Theme . find_by ( id : id ) . cached_settings . to_json
end
def included_settings ( id )
2018-07-12 12:18:21 +08:00
Theme . find_by ( id : id ) . included_settings . to_json
2018-03-05 08:04:23 +08:00
end
2018-10-16 09:00:33 +08:00
it " clears color scheme cache correctly " do
Theme . destroy_all
cs =
Fabricate (
:color_scheme ,
name : " Fancy " ,
color_scheme_colors : [
Fabricate ( :color_scheme_color , name : " header_primary " , hex : " F0F0F0 " ) ,
Fabricate ( :color_scheme_color , name : " header_background " , hex : " 1E1E1E " ) ,
Fabricate ( :color_scheme_color , name : " tertiary " , hex : " 858585 " ) ,
] ,
)
theme =
Fabricate ( :theme , user_selectable : true , user : Fabricate ( :admin ) , color_scheme_id : cs . id )
theme . set_default!
expect ( ColorScheme . hex_for_name ( " header_primary " ) ) . to eq ( " F0F0F0 " )
Theme . clear_default!
expect ( ColorScheme . hex_for_name ( " header_primary " ) ) . to eq ( " 333333 " )
end
2019-05-08 23:02:55 +08:00
it " correctly notifies about theme changes " do
cs1 = Fabricate ( :color_scheme )
cs2 = Fabricate ( :color_scheme )
theme = Fabricate ( :theme , user_selectable : true , user : user , color_scheme_id : cs1 . id )
messages = MessageBus . track_publish { theme . save! } . filter { | m | m . channel == " /file-change " }
expect ( messages . count ) . to eq ( 1 )
expect ( messages . first . data . map { | d | d [ :target ] } ) . to contain_exactly (
:desktop_theme ,
:mobile_theme ,
)
# With color scheme change:
messages =
MessageBus
. track_publish do
theme . color_scheme_id = cs2 . id
theme . save!
end
. filter { | m | m . channel == " /file-change " }
expect ( messages . count ) . to eq ( 1 )
expect ( messages . first . data . map { | d | d [ :target ] } ) . to contain_exactly (
:admin ,
:desktop ,
:desktop_theme ,
:mobile ,
:mobile_theme ,
)
end
2019-08-21 14:50:47 +08:00
it " includes theme_uploads in settings " do
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
Theme . where . not ( id : theme . id ) . destroy_all
2019-08-21 14:50:47 +08:00
2021-02-03 02:09:41 +08:00
upload = UploadCreator . new ( file_from_fixtures ( " logo.png " ) , " logo.png " ) . create_for ( - 1 )
2019-08-21 14:50:47 +08:00
theme . set_field ( type : :theme_upload_var , target : :common , name : " bob " , upload_id : upload . id )
theme . save!
json = JSON . parse ( cached_settings ( theme . id ) )
expect ( json [ " theme_uploads " ] [ " bob " ] ) . to eq ( upload . url )
end
2021-06-23 10:34:22 +08:00
it " does not break on missing uploads in settings " do
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
Theme . where . not ( id : theme . id ) . destroy_all
2021-06-23 10:34:22 +08:00
upload = UploadCreator . new ( file_from_fixtures ( " logo.png " ) , " logo.png " ) . create_for ( - 1 )
theme . set_field ( type : :theme_upload_var , target : :common , name : " bob " , upload_id : upload . id )
theme . save!
Upload . find ( upload . id ) . destroy
2022-02-19 06:30:20 +08:00
theme . remove_from_cache!
2021-06-23 10:34:22 +08:00
json = JSON . parse ( cached_settings ( theme . id ) )
expect ( json ) . to be_empty
end
2021-04-21 01:25:35 +08:00
it " uses CDN url for theme_uploads in settings " do
set_cdn_url ( " http://cdn.localhost " )
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
Theme . where . not ( id : theme . id ) . destroy_all
2021-04-21 01:25:35 +08:00
upload = UploadCreator . new ( file_from_fixtures ( " logo.png " ) , " logo.png " ) . create_for ( - 1 )
theme . set_field ( type : :theme_upload_var , target : :common , name : " bob " , upload_id : upload . id )
theme . save!
json = JSON . parse ( cached_settings ( theme . id ) )
expect ( json [ " theme_uploads " ] [ " bob " ] ) . to eq ( " http://cdn.localhost #{ upload . url } " )
end
2021-04-21 06:42:02 +08:00
it " uses CDN url for settings of type upload " do
set_cdn_url ( " http://cdn.localhost " )
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
Theme . where . not ( id : theme . id ) . destroy_all
2021-04-21 06:42:02 +08:00
upload = UploadCreator . new ( file_from_fixtures ( " logo.png " ) , " logo.png " ) . create_for ( - 1 )
theme . set_field ( target : :settings , name : " yaml " , value : << ~ YAML )
my_upload :
type : upload
default : " "
YAML
2021-09-16 10:28:53 +08:00
ThemeSetting . create! (
theme : theme ,
data_type : ThemeSetting . types [ :upload ] ,
value : upload . id . to_s ,
name : " my_upload " ,
)
2021-04-21 06:42:02 +08:00
theme . save!
json = JSON . parse ( cached_settings ( theme . id ) )
expect ( json [ " my_upload " ] ) . to eq ( " http://cdn.localhost #{ upload . url } " )
end
2019-01-17 19:46:11 +08:00
describe " theme translations " do
it " can list working theme_translation_manager objects " do
en_translation =
ThemeField . create! (
theme_id : theme . id ,
name : " en " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
value : << ~ YAML ,
en :
2019-01-25 22:19:01 +08:00
theme_metadata :
description : " Description of my theme "
2019-01-17 19:46:11 +08:00
group_of_translations :
translation1 : en test1
translation2 : en test2
base_translation1 : en test3
base_translation2 : en test4
YAML
)
fr_translation =
ThemeField . create! (
theme_id : theme . id ,
name : " fr " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
value : << ~ YAML ,
fr :
group_of_translations :
translation2 : fr test2
base_translation2 : fr test4
base_translation3 : fr test5
YAML
2023-01-09 19:18:21 +08:00
)
2019-01-17 19:46:11 +08:00
I18n . locale = :fr
theme . update_translation ( " group_of_translations.translation1 " , " overriddentest1 " )
translations = theme . translations
theme . reload
expect ( translations . map ( & :key ) ) . to eq (
%w[
group_of_translations . translation1
group_of_translations . translation2
base_translation1
base_translation2
base_translation3
] ,
)
2023-01-09 19:18:21 +08:00
2019-01-17 19:46:11 +08:00
expect ( translations . map ( & :default ) ) . to eq (
[ " en test1 " , " fr test2 " , " en test3 " , " fr test4 " , " fr test5 " ] ,
)
2023-01-09 19:18:21 +08:00
2019-01-17 19:46:11 +08:00
expect ( translations . map ( & :value ) ) . to eq (
[ " overriddentest1 " , " fr test2 " , " en test3 " , " fr test4 " , " fr test5 " ] ,
)
end
2019-01-25 22:19:01 +08:00
it " can list internal theme_translation_manager objects " do
en_translation =
ThemeField . create! (
theme_id : theme . id ,
name : " en " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
value : << ~ YAML ,
en :
theme_metadata :
description : " Description of my theme "
another_translation : en test4
YAML
2023-01-09 19:18:21 +08:00
)
2019-01-25 22:19:01 +08:00
translations = theme . internal_translations
expect ( translations . map ( & :key ) ) . to contain_exactly ( " theme_metadata.description " )
expect ( translations . map ( & :value ) ) . to contain_exactly ( " Description of my theme " )
end
2019-01-17 19:46:11 +08:00
it " can create a hash of overridden values " do
2020-05-07 04:57:14 +08:00
en_translation =
ThemeField . create! (
theme_id : theme . id ,
name : " en " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
value : << ~ YAML ,
en :
2019-01-17 19:46:11 +08:00
group_of_translations :
translation1 : en test1
YAML
2023-01-09 19:18:21 +08:00
)
2019-01-17 19:46:11 +08:00
theme . update_translation ( " group_of_translations.translation1 " , " overriddentest1 " )
I18n . locale = :fr
theme . update_translation ( " group_of_translations.translation1 " , " overriddentest2 " )
theme . reload
expect ( theme . translation_override_hash ) . to eq (
2020-05-07 04:57:14 +08:00
" en " = > {
2019-01-17 19:46:11 +08:00
" group_of_translations " = > {
" translation1 " = > " overriddentest1 " ,
} ,
} ,
" fr " = > {
" group_of_translations " = > {
" translation1 " = > " overriddentest2 " ,
} ,
} ,
)
end
it " fall back when listing baked field " do
theme2 = Fabricate ( :theme )
en_translation =
ThemeField . create! (
theme_id : theme . id ,
name : " en " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
value : " " ,
)
fr_translation =
ThemeField . create! (
theme_id : theme . id ,
name : " fr " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
value : " " ,
)
2023-01-09 19:18:21 +08:00
2019-01-17 19:46:11 +08:00
en_translation2 =
ThemeField . create! (
theme_id : theme2 . id ,
name : " en " ,
type_id : ThemeField . types [ :yaml ] ,
target_id : Theme . targets [ :translations ] ,
2021-11-22 20:16:56 +08:00
value : " " ,
2019-01-17 19:46:11 +08:00
)
2023-01-09 19:18:21 +08:00
2019-01-17 19:46:11 +08:00
expect (
Theme . list_baked_fields ( [ theme . id , theme2 . id ] , :translations , " fr " ) . map ( & :id ) ,
) . to contain_exactly ( fr_translation . id , en_translation2 . id )
end
end
2019-07-16 22:34:33 +08:00
describe " automatic recompile " do
it " must recompile after bumping theme_field version " do
child . set_field ( target : :common , name : " header " , value : " World " )
child . set_field ( target : :extra_js , name : " test.js.es6 " , value : " const hello = 'world'; " )
child . save!
first_common_value = Theme . lookup_field ( child . id , :desktop , " header " )
first_extra_js_value = Theme . lookup_field ( child . id , :extra_js , nil )
2020-05-29 20:04:51 +08:00
Theme
. stubs ( :compiler_version )
. returns ( " SOME_NEW_HASH " ) do
2019-07-16 22:34:33 +08:00
second_common_value = Theme . lookup_field ( child . id , :desktop , " header " )
second_extra_js_value = Theme . lookup_field ( child . id , :extra_js , nil )
new_common_compiler_version =
ThemeField . find_by ( theme_id : child . id , name : " header " ) . compiler_version
new_extra_js_compiler_version =
ThemeField . find_by ( theme_id : child . id , name : " test.js.es6 " ) . compiler_version
expect ( first_common_value ) . to eq ( second_common_value )
expect ( first_extra_js_value ) . to eq ( second_extra_js_value )
expect ( new_common_compiler_version ) . to eq ( " SOME_NEW_HASH " )
expect ( new_extra_js_compiler_version ) . to eq ( " SOME_NEW_HASH " )
end
end
2020-05-29 20:04:51 +08:00
it " recompiles when the hostname changes " do
theme . set_field ( target : :settings , name : :yaml , value : " name: bob " )
theme_field =
theme . set_field (
target : :common ,
name : :after_header ,
value : '<script>console.log("hello world");</script>' ,
)
theme . save!
expect ( Theme . lookup_field ( theme . id , :common , :after_header ) ) . to include (
" _ws= #{ Discourse . current_hostname } " ,
)
SiteSetting . force_hostname = " someotherhostname.com "
Theme . clear_cache!
expect ( Theme . lookup_field ( theme . id , :common , :after_header ) ) . to include (
" _ws=someotherhostname.com " ,
)
end
2019-07-16 22:34:33 +08:00
end
2021-02-03 02:09:41 +08:00
describe " extra_scss " do
let ( :scss ) { " body { background: red} " }
let ( :second_file_scss ) { " p { color: blue}; " }
let ( :child_scss ) { " body { background: green} " }
let ( :theme ) do
Fabricate ( :theme ) . tap do | t |
t . set_field ( target : :extra_scss , name : " my_files/magic " , value : scss )
t . set_field ( target : :extra_scss , name : " my_files/magic2 " , value : second_file_scss )
t . save!
2023-01-09 19:18:21 +08:00
end
end
2021-02-03 02:09:41 +08:00
let ( :child_theme ) do
Fabricate ( :theme ) . tap do | t |
t . component = true
t . set_field ( target : :extra_scss , name : " my_files/moremagic " , value : child_scss )
t . save!
theme . add_relative_theme! ( :child , t )
2023-01-09 19:18:21 +08:00
end
end
2021-02-03 02:09:41 +08:00
let ( :compiler ) do
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : theme . id )
builder =
Stylesheet :: Manager :: Builder . new ( target : :desktop_theme , theme : theme , manager : manager )
builder . compile ( force : true )
2023-01-09 19:18:21 +08:00
end
2021-02-03 02:09:41 +08:00
it " works when importing file by path " do
theme . set_field ( target : :common , name : :scss , value : '@import "my_files/magic";' )
theme . save!
css , _map = compiler
expect ( css ) . to include ( " body{background:red} " )
end
it " works when importing multiple files " do
theme . set_field (
target : :common ,
name : :scss ,
value : '@import "my_files/magic"; @import "my_files/magic2"' ,
)
theme . save!
css , _map = compiler
expect ( css ) . to include ( " body{background:red} " )
expect ( css ) . to include ( " p{color:blue} " )
end
2021-02-27 01:30:23 +08:00
it " works for child themes " do
2021-02-03 02:09:41 +08:00
child_theme . set_field ( target : :common , name : :scss , value : '@import "my_files/moremagic"' )
child_theme . save!
2021-06-15 14:57:17 +08:00
manager = Stylesheet :: Manager . new ( theme_id : child_theme . id )
builder =
Stylesheet :: Manager :: Builder . new (
target : :desktop_theme ,
theme : child_theme ,
manager : manager ,
)
css , _map = builder . compile ( force : true )
2021-02-03 02:09:41 +08:00
expect ( css ) . to include ( " body{background:green} " )
2021-02-04 21:51:18 +08:00
end
2021-02-03 02:09:41 +08:00
end
describe " scss_variables " do
it " is empty by default " do
expect ( theme . scss_variables ) . to eq ( nil )
end
it " includes settings and uploads when set " do
theme . set_field (
target : :settings ,
name : :yaml ,
value : " background_color: red \n font_size: 25px " ,
)
upload = UploadCreator . new ( file_from_fixtures ( " logo.png " ) , " logo.png " ) . create_for ( - 1 )
theme . set_field ( type : :theme_upload_var , target : :common , name : " bobby " , upload_id : upload . id )
theme . save!
expect ( theme . scss_variables ) . to include ( " $background_color: unquote( \" red \" ) " )
expect ( theme . scss_variables ) . to include ( " $font_size: unquote( \" 25px \" ) " )
expect ( theme . scss_variables ) . to include ( " $bobby: " )
end
end
2021-04-29 04:12:08 +08:00
describe " # baked_js_tests_with_digest " do
before do
ThemeField . create! (
theme_id : theme . id ,
target_id : Theme . targets [ :settings ] ,
name : " yaml " ,
value : " some_number: 1 " ,
)
2024-01-16 09:50:44 +08:00
2021-04-29 04:12:08 +08:00
theme . set_field (
target : :tests_js ,
type : :js ,
name : " acceptance/some-test.js " ,
value : " assert.ok(true); " ,
)
2024-01-16 09:50:44 +08:00
2021-04-29 04:12:08 +08:00
theme . save!
end
it " returns nil for content and digest if theme does not have tests " do
ThemeField . destroy_all
expect ( theme . baked_js_tests_with_digest ) . to eq ( [ nil , nil ] )
end
2024-01-16 09:50:44 +08:00
it " includes theme's migrations theme fields " do
theme . set_field (
target : :migrations ,
type : :js ,
name : " 0001-some-migration " ,
value : " export default function migrate(settings) { return settings; } " ,
)
theme . save!
content , _digest = theme . baked_js_tests_with_digest
expect ( content ) . to include ( " function migrate(settings) " )
end
2021-04-29 04:12:08 +08:00
it " digest does not change when settings are changed " do
content , digest = theme . baked_js_tests_with_digest
expect ( content ) . to be_present
expect ( digest ) . to be_present
expect ( content ) . to include ( " assert.ok(true); " )
theme . update_setting ( :some_number , 55 )
theme . save!
expect ( theme . build_settings_hash [ :some_number ] ) . to eq ( 55 )
new_content , new_digest = theme . baked_js_tests_with_digest
expect ( new_content ) . to eq ( content )
expect ( new_digest ) . to eq ( digest )
end
end
2021-11-22 20:16:56 +08:00
2023-10-23 07:41:40 +08:00
describe " get_setting " do
before do
theme . set_field ( target : :settings , name : " yaml " , value : << ~ YAML )
enabled :
type : bool
default : false
some_value :
type : string
default : " hello "
YAML
ThemeSetting . create! (
theme : theme ,
data_type : ThemeSetting . types [ :bool ] ,
name : " super_feature_enabled " ,
)
theme . save!
end
it " returns the value of the setting when given a string represeting the setting name " do
expect ( theme . get_setting ( " enabled " ) ) . to eq ( false )
expect ( theme . get_setting ( " some_value " ) ) . to eq ( " hello " )
end
it " returns the value of the setting when given a symbol represeting the setting name " do
expect ( theme . get_setting ( :enabled ) ) . to eq ( false )
expect ( theme . get_setting ( :some_value ) ) . to eq ( " hello " )
end
end
2021-11-22 20:16:56 +08:00
describe " # update_setting " do
it " requests clients to refresh if `refresh: true` " do
theme . set_field ( target : :settings , name : " yaml " , value : << ~ YAML )
super_feature_enabled :
type : bool
default : false
refresh : true
YAML
ThemeSetting . create! (
theme : theme ,
data_type : ThemeSetting . types [ :bool ] ,
name : " super_feature_enabled " ,
)
theme . save!
messages =
MessageBus
. track_publish do
theme . update_setting ( :super_feature_enabled , true )
theme . save!
end
. filter { | m | m . channel == " /global/asset-version " }
expect ( messages . count ) . to eq ( 1 )
end
it " does not request clients to refresh if `refresh: false` " do
theme . set_field ( target : :settings , name : " yaml " , value : << ~ YAML )
super_feature_enabled :
type : bool
default : false
refresh : false
YAML
ThemeSetting . create! (
theme : theme ,
data_type : ThemeSetting . types [ :bool ] ,
name : " super_feature_enabled " ,
)
theme . save!
messages =
MessageBus
. track_publish do
theme . update_setting ( :super_feature_enabled , true )
theme . save!
end
. filter { | m | m . channel == " /global/asset-version " }
expect ( messages . count ) . to eq ( 0 )
end
end
2023-08-04 18:02:26 +08:00
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
describe " # migrate_settings " do
fab! ( :settings_field ) { Fabricate ( :settings_theme_field , theme : theme , value : << ~ YAML ) }
integer_setting : 1
list_setting : " aa,bb "
YAML
fab! ( :migration_field ) { Fabricate ( :migration_theme_field , theme : theme , version : 1 ) }
it " persists the results of the last pending migration to the database " do
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 1033 ) ;
settings . set ( " list_setting " , " cc,dd " ) ;
return settings ;
}
JS
Fabricate ( :migration_theme_field , theme : theme , value : << ~ JS , version : 2 )
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 9909 ) ;
settings . set ( " list_setting " , " ee,ff " ) ;
return settings ;
}
JS
theme . migrate_settings
expect ( theme . get_setting ( " integer_setting " ) ) . to eq ( 9909 )
expect ( theme . get_setting ( " list_setting " ) ) . to eq ( " ee,ff " )
end
it " doesn't allow arbitrary settings to be saved in the database " do
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
settings . set ( " unknown_setting " , 8834 ) ;
return settings ;
}
JS
expect do theme . migrate_settings end . to raise_error (
Theme :: SettingsMigrationError ,
I18n . t (
" themes.import_error.migrations.unknown_setting_returned_by_migration " ,
name : " 0001-some-name " ,
setting_name : " unknown_setting " ,
) ,
)
end
2024-02-05 14:35:11 +08:00
it " updates the theme's javascript cache after running migration " do
theme . set_field ( target : :extra_js , name : " test.js.es6 " , value : " const hello = 'world'; " )
theme . save!
expect ( theme . javascript_cache . content ) . to include ( '"list_setting":"aa,bb"' )
settings_field . update! ( value : << ~ YAML )
integer_setting : 1
list_setting :
default : aa | bb
type : list
YAML
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
settings . set ( " list_setting " , " zz|aa " ) ;
return settings ;
}
JS
theme . reload
theme . migrate_settings
setting_record = theme . theme_settings . where ( name : " list_setting " ) . first
expect ( setting_record . data_type ) . to eq ( ThemeSetting . types [ :list ] )
expect ( setting_record . value ) . to eq ( " zz|aa " )
expect ( theme . javascript_cache . content ) . to include ( '"list_setting":"zz|aa"' )
end
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
it " allows changing a setting's type " do
theme . update_setting ( :list_setting , " zz,aa " )
theme . save!
setting_record = theme . theme_settings . where ( name : " list_setting " ) . first
2024-02-05 14:35:11 +08:00
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
expect ( setting_record . data_type ) . to eq ( ThemeSetting . types [ :string ] )
expect ( setting_record . value ) . to eq ( " zz,aa " )
settings_field . update! ( value : << ~ YAML )
integer_setting : 1
list_setting :
default : aa | bb
type : list
YAML
2024-02-05 14:35:11 +08:00
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
settings . set ( " list_setting " , " zz|aa " ) ;
return settings ;
}
JS
2024-02-05 14:35:11 +08:00
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
theme . reload
theme . migrate_settings
expect ( theme . theme_settings . where ( name : " list_setting " ) . count ) . to eq ( 1 )
setting_record = theme . theme_settings . where ( name : " list_setting " ) . first
expect ( setting_record . data_type ) . to eq ( ThemeSetting . types [ :list ] )
expect ( setting_record . value ) . to eq ( " zz|aa " )
expect (
theme . theme_settings_migrations . where ( theme_field_id : migration_field . id ) . first . diff ,
) . to eq (
" additions " = > [ { " key " = > " list_setting " , " val " = > " zz|aa " } ] ,
" deletions " = > [ { " key " = > " list_setting " , " val " = > " zz,aa " } ] ,
)
end
it " allows renaming a setting " do
theme . update_setting ( :integer_setting , 11 )
theme . save!
setting_record = theme . theme_settings . where ( name : " integer_setting " ) . first
expect ( setting_record . value ) . to eq ( " 11 " )
settings_field . update! ( value : << ~ YAML )
integer_setting_updated : 1
list_setting : " aa,bb "
YAML
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
settings . set ( " integer_setting_updated " , settings . get ( " integer_setting " ) ) ;
return settings ;
}
JS
theme . reload
theme . migrate_settings
expect ( theme . theme_settings . where ( name : " integer_setting " ) . exists? ) . to eq ( false )
setting_record = theme . theme_settings . where ( name : " integer_setting_updated " ) . first
expect ( setting_record . value ) . to eq ( " 11 " )
expect (
theme . theme_settings_migrations . where ( theme_field_id : migration_field . id ) . first . diff ,
) . to eq (
" additions " = > [ { " key " = > " integer_setting_updated " , " val " = > 11 } ] ,
" deletions " = > [ { " key " = > " integer_setting " , " val " = > 11 } ] ,
)
end
it " creates a ThemeSettingsMigration record for each migration " do
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 2 ) ;
settings . set ( " list_setting " , " cc,dd " ) ;
return settings ;
}
JS
second_migration_field =
Fabricate ( :migration_theme_field , theme : theme , value : << ~ JS , version : 2 )
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 3 ) ;
settings . set ( " list_setting " , " ee,ff " ) ;
return settings ;
}
JS
third_migration_field =
Fabricate ( :migration_theme_field , theme : theme , value : << ~ JS , version : 3 )
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 4 ) ;
settings . set ( " list_setting " , " gg,hh " ) ;
return settings ;
}
JS
theme . migrate_settings
records = theme . theme_settings_migrations . order ( :version )
expect ( records . count ) . to eq ( 3 )
expect ( records [ 0 ] . version ) . to eq ( 1 )
expect ( records [ 0 ] . name ) . to eq ( " some-name " )
expect ( records [ 0 ] . theme_field_id ) . to eq ( migration_field . id )
expect ( records [ 0 ] . diff ) . to eq (
" additions " = > [
{ " key " = > " integer_setting " , " val " = > 2 } ,
{ " key " = > " list_setting " , " val " = > " cc,dd " } ,
] ,
" deletions " = > [ ] ,
)
expect ( records [ 1 ] . version ) . to eq ( 2 )
expect ( records [ 1 ] . name ) . to eq ( " some-name " )
expect ( records [ 1 ] . theme_field_id ) . to eq ( second_migration_field . id )
expect ( records [ 1 ] . diff ) . to eq (
" additions " = > [
{ " key " = > " integer_setting " , " val " = > 3 } ,
{ " key " = > " list_setting " , " val " = > " ee,ff " } ,
] ,
" deletions " = > [
{ " key " = > " integer_setting " , " val " = > 2 } ,
{ " key " = > " list_setting " , " val " = > " cc,dd " } ,
] ,
)
expect ( records [ 2 ] . version ) . to eq ( 3 )
expect ( records [ 2 ] . name ) . to eq ( " some-name " )
expect ( records [ 2 ] . theme_field_id ) . to eq ( third_migration_field . id )
expect ( records [ 2 ] . diff ) . to eq (
" additions " = > [
{ " key " = > " integer_setting " , " val " = > 4 } ,
{ " key " = > " list_setting " , " val " = > " gg,hh " } ,
] ,
" deletions " = > [
{ " key " = > " integer_setting " , " val " = > 3 } ,
{ " key " = > " list_setting " , " val " = > " ee,ff " } ,
] ,
)
end
it " allows removing an old setting that no longer exists " do
settings_field . update! ( value : << ~ YAML )
setting_that_will_be_removed : 1
YAML
theme . update_setting ( :setting_that_will_be_removed , 1023 )
theme . save!
settings_field . update! ( value : << ~ YAML )
new_setting : 1
YAML
migration_field . update! ( value : << ~ JS )
export default function migrate ( settings ) {
if ( settings . get ( " setting_that_will_be_removed " ) != = 1023 ) {
throw new Error ( ` expected setting_that_will_be_removed to be 1023, but it was instead ${settings.get("setting_that_will_be_removed")}. ` ) ;
}
settings . delete ( " setting_that_will_be_removed " ) ;
return settings ;
}
JS
theme . reload
theme . migrate_settings
theme . reload
expect ( theme . theme_settings . count ) . to eq ( 0 )
records = theme . theme_settings_migrations
expect ( records . size ) . to eq ( 1 )
expect ( records [ 0 ] . diff ) . to eq (
" additions " = > [ ] ,
" deletions " = > [ { " key " = > " setting_that_will_be_removed " , " val " = > 1023 } ] ,
)
end
DEV: Introduce `run_theme_migration` spec helper in test environment (#26845)
This commit introduces the `run_theme_migration` spec helper to allow
theme developers to write RSpec tests for theme migrations. For example,
this allows the following RSpec test to be written in themes:
```
RSpec.describe "0003-migrate-small-links-setting migration" do
let!(:theme) { upload_theme_component }
it "should set target property to `_blank` if previous target component is not valid or empty" do
theme.theme_settings.create!(
name: "small_links",
theme: theme,
data_type: ThemeSetting.types[:string],
value: "some text, #|some text 2, #, invalid target",
)
run_theme_migration(theme, "0003-migrate-small-links-setting")
expect(theme.settings[:small_links].value).to eq(
[
{ "text" => "some text", "url" => "#", "target" => "_blank" },
{ "text" => "some text 2", "url" => "#", "target" => "_blank" },
],
)
end
end
```
This change is being introduced because we realised that writting just
javascript tests for the migrations is insufficient since javascript
tests do not ensure that the migrated theme settings can actually be
successfully saved into the database. Hence, we are introduce this
helper as a way for theme developers to write "end-to-end" migrations
tests.
2024-05-03 06:29:18 +08:00
it " does not raise an out of sequence error and does not create `ThemeSettingsMigration` record for out of sequence migration when `allow_out_of_sequence_migration` kwarg is set to true " do
second_migration_field =
Fabricate (
:migration_theme_field ,
name : " 0001-some-other-migration-name " ,
theme : theme ,
value : << ~ JS ,
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 3 ) ;
return settings ;
}
JS
version : 1 ,
)
expect do theme . migrate_settings end . to raise_error (
Theme :: SettingsMigrationError ,
/ '0001-some-other-migration-name' is out of sequence / ,
)
expect ( theme . get_setting ( " integer_setting " ) ) . to eq ( 1 )
theme . migrate_settings ( allow_out_of_sequence_migration : true )
expect ( theme . theme_settings_migrations . count ) . to eq ( 1 )
expect ( theme . theme_settings_migrations . first . theme_field_id ) . to eq ( migration_field . id )
expect ( theme . get_setting ( " integer_setting " ) ) . to eq ( 3 )
end
it " allows custom migration fields to be run by specifing the `fields` kwarg " do
expect do theme . migrate_settings ( fields : [ ] ) end . not_to change {
theme . theme_settings_migrations . count
}
second_migration_field =
Fabricate ( :migration_theme_field , theme : theme , value : << ~ JS , version : 2 )
export default function migrate ( settings ) {
settings . set ( " integer_setting " , 3 ) ;
return settings ;
}
JS
theme . migrate_settings ( fields : [ second_migration_field ] )
expect ( theme . theme_settings_migrations . count ) . to eq ( 1 )
expect ( theme . theme_settings_migrations . first . theme_field_id ) . to eq ( second_migration_field . id )
expect ( theme . get_setting ( " integer_setting " ) ) . to eq ( 3 )
end
FEATURE: Theme settings migrations (#24071)
This commit introduces a new feature that allows theme developers to manage the transformation of theme settings over time. Similar to Rails migrations, the theme settings migration system enables developers to write and execute migrations for theme settings, ensuring a smooth transition when changes are required in the format or structure of setting values.
Example use cases for the theme settings migration system:
1. Renaming a theme setting.
2. Changing the data type of a theme setting (e.g., transforming a string setting containing comma-separated values into a proper list setting).
3. Altering the format of data stored in a theme setting.
All of these use cases and more are now possible while preserving theme setting values for sites that have already modified their theme settings.
Usage:
1. Create a top-level directory called `migrations` in your theme/component, and then within the `migrations` directory create another directory called `settings`.
2. Inside the `migrations/settings` directory, create a JavaScript file using the format `XXXX-some-name.js`, where `XXXX` is a unique 4-digit number, and `some-name` is a descriptor of your choice that describes the migration.
3. Within the JavaScript file, define and export (as the default) a function called `migrate`. This function will receive a `Map` object and must also return a `Map` object (it's acceptable to return the same `Map` object that the function received).
4. The `Map` object received by the `migrate` function will include settings that have been overridden or changed by site administrators. Settings that have never been changed from the default will not be included.
5. The keys and values contained in the `Map` object that the `migrate` function returns will replace all the currently changed settings of the theme.
6. Migrations are executed in numerical order based on the XXXX segment in the migration filenames. For instance, `0001-some-migration.js` will be executed before `0002-another-migration.js`.
Here's a complete example migration script that renames a setting from `setting_with_old_name` to `setting_with_new_name`:
```js
// File name: 0001-rename-setting.js
export default function migrate(settings) {
if (settings.has("setting_with_old_name")) {
settings.set("setting_with_new_name", settings.get("setting_with_old_name"));
}
return settings;
}
```
Internal topic: t/109980
2023-11-02 13:10:15 +08:00
end
2023-08-04 18:02:26 +08:00
describe " development experience " do
it " sends 'development-mode-theme-changed event when non-css fields are updated " do
Theme . any_instance . stubs ( :should_refresh_development_clients? ) . returns ( true )
theme . set_field ( target : :common , name : :scss , value : " body {background: green;} " )
messages =
MessageBus
. track_publish { theme . save! }
. filter { | m | m . channel == " /file-change " }
. map ( & :data )
expect ( messages ) . not_to include ( " development-mode-theme-changed " )
theme . set_field ( target : :common , name : :header , value : " <p>Hello world</p> " )
messages =
MessageBus
. track_publish { theme . save! }
. filter { | m | m . channel == " /file-change " }
. map ( & :data )
expect ( messages ) . to include ( [ " development-mode-theme-changed " ] )
end
end
2023-09-01 03:12:03 +08:00
describe " # lookup_field when a theme component is used in multiple themes " do
fab! ( :theme_1 ) { Fabricate ( :theme , user : user ) }
fab! ( :theme_2 ) { Fabricate ( :theme , user : user ) }
fab! ( :child ) { Fabricate ( :theme , user : user , component : true ) }
before_all do
theme_1 . add_relative_theme! ( :child , child )
theme_2 . add_relative_theme! ( :child , child )
end
it " efficiently caches fields of theme component by only caching the fields once across multiple themes " do
child . set_field ( target : :common , name : " header " , value : " World " )
child . save!
expect ( Theme . lookup_field ( theme_1 . id , :desktop , " header " ) ) . to eq ( " World " )
expect ( Theme . lookup_field ( theme_2 . id , :desktop , " header " ) ) . to eq ( " World " )
expect (
Theme . cache . defer_get_set ( " #{ child . id } :common:header: #{ Theme . compiler_version } " ) { raise } ,
) . to eq ( [ " World " ] )
expect (
Theme . cache . defer_get_set ( " #{ child . id } :desktop:header: #{ Theme . compiler_version } " ) { raise } ,
) . to eq ( nil )
expect (
Theme
. cache
. defer_get_set ( " #{ theme_1 . id } :common:header: #{ Theme . compiler_version } " ) { raise } ,
) . to eq ( nil )
expect (
Theme
. cache
. defer_get_set ( " #{ theme_1 . id } :desktop:header: #{ Theme . compiler_version } " ) { raise } ,
) . to eq ( nil )
expect (
Theme
. cache
. defer_get_set ( " #{ theme_2 . id } :common:header: #{ Theme . compiler_version } " ) { raise } ,
) . to eq ( nil )
expect (
Theme
. cache
. defer_get_set ( " #{ theme_2 . id } :desktop:header: #{ Theme . compiler_version } " ) { raise } ,
) . to eq ( nil )
end
it " puts the parent value ahead of the child " do
theme_1 . set_field ( target : :common , name : " header " , value : " theme_1 " )
theme_1 . save!
child . set_field ( target : :common , name : " header " , value : " child " )
child . save!
expect ( Theme . lookup_field ( theme_1 . id , :desktop , " header " ) ) . to eq ( " theme_1 \n child " )
end
it " puts parent translations ahead of child translations " do
theme_1 . set_field ( target : :translations , name : " en " , value : << ~ YAML )
en :
theme_1 : " test "
YAML
theme_1 . save!
theme_field = ThemeField . order ( :id ) . last
child . set_field ( target : :translations , name : " en " , value : << ~ YAML )
en :
child : " test "
YAML
child . save!
child_field = ThemeField . order ( :id ) . last
expect ( theme_field . value_baked ) . not_to eq ( child_field . value_baked )
expect ( Theme . lookup_field ( theme_1 . id , :translations , :en ) ) . to eq (
[ theme_field , child_field ] . map ( & :value_baked ) . join ( " \n " ) ,
)
end
it " prioritizes a locale over its fallback " do
theme_1 . set_field ( target : :translations , name : " en " , value : << ~ YAML )
en :
theme_1 : " hello "
YAML
theme_1 . save!
en_field = ThemeField . order ( :id ) . last
theme_1 . set_field ( target : :translations , name : " es " , value : << ~ YAML )
es :
theme_1 : " hola "
YAML
theme_1 . save!
es_field = ThemeField . order ( :id ) . last
expect ( es_field . value_baked ) . not_to eq ( en_field . value_baked )
expect ( Theme . lookup_field ( theme_1 . id , :translations , :en ) ) . to eq ( en_field . value_baked )
expect ( Theme . lookup_field ( theme_1 . id , :translations , :es ) ) . to eq ( es_field . value_baked )
expect ( Theme . lookup_field ( theme_1 . id , :translations , :fr ) ) . to eq ( en_field . value_baked )
end
end
2024-03-21 00:00:43 +08:00
describe " # repository_url " do
subject ( :repository_url ) { theme . repository_url }
context " when theme is not a remote one " do
it " returns nothing " do
expect ( repository_url ) . to be_blank
end
end
context " when theme is a remote one " do
let! ( :remote_theme ) { theme . create_remote_theme ( remote_url : remote_url ) }
context " when URL is a SSH one " do
let ( :remote_url ) { " git@github.com:discourse/graceful.git " }
it " normalizes it " do
expect ( repository_url ) . to eq " github.com/discourse/graceful "
end
end
context " when URL is a HTTPS one " do
let ( :remote_url ) { " https://github.com/discourse/graceful.git " }
it " normalizes it " do
expect ( repository_url ) . to eq " github.com/discourse/graceful "
end
end
context " when URL is a HTTP one " do
let ( :remote_url ) { " http://github.com/discourse/graceful " }
it " normalizes it " do
expect ( repository_url ) . to eq " github.com/discourse/graceful "
end
end
context " when URL contains query params " do
let ( :remote_url ) { " http://github.com/discourse/graceful.git?param_id=1 " }
it " keeps the query params " do
expect ( repository_url ) . to eq " github.com/discourse/graceful?param_id=1 "
end
end
end
end
describe " # user_selectable_count " do
subject ( :count ) { theme . user_selectable_count }
let! ( :users ) { Fabricate . times ( 5 , :user ) }
let! ( :another_theme ) { Fabricate ( :theme ) }
before do
users . take ( 3 ) . each { _1 . user_option . update! ( theme_ids : [ theme . id ] ) }
users . slice ( 3 .. 4 ) . each { _1 . user_option . update! ( theme_ids : [ another_theme . id ] ) }
end
it " returns how many users are currently using the theme " do
expect ( count ) . to eq 3
end
end
2017-04-12 22:52:52 +08:00
end