From 3f6e3b9affc973fa2d9602ffe285d2a057adf685 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 2 Sep 2016 11:42:14 -0400 Subject: [PATCH] Wizard - Color Scheme Step --- .../wizard/components/scheme-preview.js.es6 | 199 ++++++++++++++++++ .../wizard/components/wizard-field.js.es6 | 7 +- .../javascripts/wizard/models/step.js.es6 | 7 + .../templates/components/scheme-preview.hbs | 4 + .../components/wizard-field-dropdown.hbs | 2 +- .../templates/components/wizard-field.hbs | 2 +- .../templates/components/wizard-step.hbs | 2 +- .../wizard/test/acceptance/wizard-test.js.es6 | 7 +- .../javascripts/wizard/test/test_helper.js | 1 + .../wizard/test/wizard-pretender.js.es6 | 5 +- app/assets/stylesheets/wizard.scss | 3 + app/models/color_scheme.rb | 33 ++- app/serializers/wizard_field_serializer.rb | 12 +- config/locales/server.en.yml | 4 +- ...6200439_add_via_wizard_to_color_schemes.rb | 5 + lib/wizard.rb | 5 +- lib/wizard/field.rb | 6 +- lib/wizard/step_updater.rb | 28 +++ public/images/wizard/discourse-small.png | Bin 0 -> 1318 bytes public/images/wizard/trout.png | Bin 0 -> 8844 bytes spec/components/step_updater_spec.rb | 34 ++- 21 files changed, 343 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/wizard/components/scheme-preview.js.es6 create mode 100644 app/assets/javascripts/wizard/templates/components/scheme-preview.hbs create mode 100644 db/migrate/20160906200439_add_via_wizard_to_color_schemes.rb create mode 100644 public/images/wizard/discourse-small.png create mode 100644 public/images/wizard/trout.png diff --git a/app/assets/javascripts/wizard/components/scheme-preview.js.es6 b/app/assets/javascripts/wizard/components/scheme-preview.js.es6 new file mode 100644 index 00000000000..484dafab5b1 --- /dev/null +++ b/app/assets/javascripts/wizard/components/scheme-preview.js.es6 @@ -0,0 +1,199 @@ +/*eslint no-bitwise:0 */ + +import { observes } from 'ember-addons/ember-computed-decorators'; + +const WIDTH = 400; +const HEIGHT = 220; +const LINE_HEIGHT = 12.0; + +const LOREM = ` +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Nullam eget sem non elit tincidunt rhoncus. Fusce velit nisl, +porttitor sed nisl ac, consectetur interdum metus. Fusce in +consequat augue, vel facilisis felis. Nunc tellus elit, and +semper vitae orci nec, blandit pharetra enim. Aenean a ebus +posuere nunc. Maecenas ultrices viverra enim ac commodo +Vestibulum nec quam sit amet libero ultricies sollicitudin. +Nulla quis scelerisque sem, eget volutpat velit. Fusce eget +accumsan sapien, nec feugiat quam. Quisque non risus. +placerat lacus vitae, lacinia nisi. Sed metus arcu, iaculis +sit amet cursus nec, sodales at eros.`; + +function loadImage(src) { + const img = new Image(); + img.src = src; + + return new Ember.RSVP.Promise(resolve => img.onload = () => resolve(img)); +}; + +function parseColor(color) { + const m = color.match(/^#([0-9a-f]{6})$/i); + if (m) { + const c = m[1]; + return [ parseInt(c.substr(0,2),16), parseInt(c.substr(2,2),16), parseInt(c.substr(4,2),16) ]; + } + + return [0, 0, 0]; +} + +function brightness(color) { + return (color[0] * 0.299) + (color[1] * 0.587) + (color[2] * 0.114); +} + +function lighten(color, percent) { + return '#' + + ((0|(1<<8) + color[0] + (256 - color[0]) * percent / 100).toString(16)).substr(1) + + ((0|(1<<8) + color[1] + (256 - color[1]) * percent / 100).toString(16)).substr(1) + + ((0|(1<<8) + color[2] + (256 - color[2]) * percent / 100).toString(16)).substr(1); +} + +function chooseBrighter(primary, secondary) { + const primaryCol = parseColor(primary); + const secondaryCol = parseColor(secondary); + + return brightness(primaryCol) < brightness(secondaryCol) ? secondary : primary; +} + +function darkLightDiff(adjusted, comparison, lightness, darkness) { + const adjustedCol = parseColor(adjusted); + const comparisonCol = parseColor(comparison); + return lighten(adjustedCol, (brightness(adjustedCol) < brightness(comparisonCol)) ? + lightness : darkness); +} + +export default Ember.Component.extend({ + ctx: null, + width: WIDTH, + height: HEIGHT, + loaded: false, + logo: null, + + colorScheme: Ember.computed.alias('step.fieldsById.color_scheme.value'), + + didInsertElement() { + this._super(); + const c = this.$('canvas')[0]; + this.ctx = c.getContext("2d"); + + Ember.RSVP.Promise.all([loadImage('/images/wizard/discourse-small.png'), + loadImage('/images/wizard/trout.png')]).then(result => { + this.logo = result[0]; + this.avatar = result[1]; + this.loaded = true; + this.triggerRepaint(); + }); + }, + + @observes('colorScheme') + triggerRepaint() { + Ember.run.scheduleOnce('afterRender', this, 'repaint'); + }, + + repaint() { + if (!this.loaded) { return; } + + const { ctx } = this; + const headerHeight = HEIGHT * 0.15; + + const colorScheme = this.get('colorScheme'); + const options = this.get('step.fieldsById.color_scheme.options'); + const option = options.findProperty('id', colorScheme); + if (!option) { return; } + + const colors = option.data.colors; + if (!colors) { return; } + + ctx.fillStyle = colors.secondary; + ctx.fillRect(0, 0, WIDTH, HEIGHT); + + // Header area + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, WIDTH, headerHeight); + ctx.fillStyle = colors.header_background; + ctx.shadowColor = "rgba(0, 0, 0, 0.25)"; + ctx.shadowBlur = 2; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 2; + ctx.fill(); + ctx.restore(); + + const margin = WIDTH * 0.02; + const avatarSize = HEIGHT * 0.1; + + // Logo + const headerMargin = headerHeight * 0.2; + const logoHeight = headerHeight - (headerMargin * 2); + const logoWidth = (logoHeight / this.logo.height) * this.logo.width; + ctx.drawImage(this.logo, headerMargin, headerMargin, logoWidth, logoHeight); + + // Top right menu + ctx.drawImage(this.avatar, WIDTH - avatarSize - headerMargin, headerMargin, avatarSize, avatarSize); + ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 45, 55); + ctx.font = "0.75em FontAwesome"; + ctx.fillText("\uf0c9", WIDTH - (avatarSize * 2) - (headerMargin * 0.5), avatarSize); + ctx.fillText("\uf002", WIDTH - (avatarSize * 3) - (headerMargin * 0.5), avatarSize); + + // Draw a fake topic + ctx.drawImage(this.avatar, margin, headerHeight + (HEIGHT * 0.17), avatarSize, avatarSize); + + ctx.beginPath(); + ctx.fillStyle = colors.primary; + ctx.font = "bold 0.75em 'Arial'"; + ctx.fillText("Welcome to Discourse", margin, (HEIGHT * 0.25)); + + ctx.font = "0.5em 'Arial'"; + + let line = 0; + + const lines = LOREM.split("\n"); + for (let i=0; i<10; i++) { + line = (HEIGHT * 0.3) + (i * LINE_HEIGHT); + ctx.fillText(lines[i], margin + avatarSize + margin, line); + } + + // Reply Button + ctx.beginPath(); + ctx.rect(WIDTH * 0.57, line + LINE_HEIGHT, WIDTH * 0.1, HEIGHT * 0.07); + ctx.fillStyle = colors.tertiary; + ctx.fill(); + ctx.fillStyle = chooseBrighter(colors.primary, colors.secondary); + ctx.font = "8px 'Arial'"; + ctx.fillText("Reply", WIDTH * 0.595, line + (LINE_HEIGHT * 1.8)); + + // Icons + ctx.font = "0.5em FontAwesome"; + ctx.fillStyle = colors.love; + ctx.fillText("\uf004", WIDTH * 0.48, line + (LINE_HEIGHT * 1.8)); + ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 65, 55); + ctx.fillText("\uf040", WIDTH * 0.525, line + (LINE_HEIGHT * 1.8)); + + // Draw Timeline + const timelineX = WIDTH * 0.8; + ctx.beginPath(); + ctx.strokeStyle = colors.tertiary; + ctx.lineWidth = 0.5; + ctx.moveTo(timelineX, HEIGHT * 0.3); + ctx.lineTo(timelineX, HEIGHT * 0.6); + ctx.stroke(); + + // Timeline + ctx.beginPath(); + ctx.strokeStyle = colors.tertiary; + ctx.lineWidth = 2; + ctx.moveTo(timelineX, HEIGHT * 0.3); + ctx.lineTo(timelineX, HEIGHT * 0.4); + ctx.stroke(); + + ctx.font = "Bold 0.5em Arial"; + ctx.fillStyle = colors.primary; + ctx.fillText("1 / 20", timelineX + margin, (HEIGHT * 0.3) + (margin * 1.5)); + + // draw border + ctx.beginPath(); + ctx.strokeStyle='rgba(0, 0, 0, 0.2)'; + ctx.rect(0, 0, WIDTH, HEIGHT); + ctx.stroke(); + } + +}); diff --git a/app/assets/javascripts/wizard/components/wizard-field.js.es6 b/app/assets/javascripts/wizard/components/wizard-field.js.es6 index cb4c67de9d0..67b65ee47b7 100644 --- a/app/assets/javascripts/wizard/components/wizard-field.js.es6 +++ b/app/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -6,6 +6,9 @@ export default Ember.Component.extend({ @computed('field.id') inputClassName: id => `field-${Ember.String.dasherize(id)}`, - @computed('field.type') - inputComponentName: type => `wizard-field-${type}` + @computed('field.type', 'field.id') + inputComponentName(type, id) { + return (type === 'component') ? Ember.String.dasherize(id) : `wizard-field-${type}`; + } + }); diff --git a/app/assets/javascripts/wizard/models/step.js.es6 b/app/assets/javascripts/wizard/models/step.js.es6 index 5fa3b61becb..a2ba11408e5 100644 --- a/app/assets/javascripts/wizard/models/step.js.es6 +++ b/app/assets/javascripts/wizard/models/step.js.es6 @@ -8,6 +8,13 @@ export default Ember.Object.extend(ValidState, { @computed('index') displayIndex: index => index + 1, + @computed('fields.[]') + fieldsById(fields) { + const lookup = {}; + fields.forEach(field => lookup[field.get('id')] = field); + return lookup; + }, + checkFields() { let allValid = true; this.get('fields').forEach(field => { diff --git a/app/assets/javascripts/wizard/templates/components/scheme-preview.hbs b/app/assets/javascripts/wizard/templates/components/scheme-preview.hbs new file mode 100644 index 00000000000..a4950856af0 --- /dev/null +++ b/app/assets/javascripts/wizard/templates/components/scheme-preview.hbs @@ -0,0 +1,4 @@ +
+ + +
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 8339c52697f..4f843aa3edd 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -1 +1 @@ -{{combo-box value=field.value content=field.options nameProperty="label" width="400px"}} +{{combo-box class=inputClassName value=field.value content=field.options nameProperty="label" width="400px"}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field.hbs index d757791f83d..973630851f0 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -2,7 +2,7 @@ {{field.label}}
- {{component inputComponentName field=field inputClassName=inputClassName}} + {{component inputComponentName field=field step=step inputClassName=inputClassName}}
{{#if field.errorDescription}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs b/app/assets/javascripts/wizard/templates/components/wizard-step.hbs index ff6a901208b..52d174d0627 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-step.hbs @@ -8,7 +8,7 @@ {{#wizard-step-form step=step}} {{#each step.fields as |field|}} - {{wizard-field field=field}} + {{wizard-field field=field step=step}} {{/each}} {{/wizard-step-form}} diff --git a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js.es6 b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js.es6 index 345f613563c..af59f65dd79 100644 --- a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js.es6 +++ b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js.es6 @@ -8,7 +8,7 @@ test("Wizard starts", assert => { }); }); -test("Forum Name Step", assert => { +test("Going back and forth in steps", assert => { visit("/step/hello-world"); andThen(() => { assert.ok(exists('.wizard-step')); @@ -44,7 +44,10 @@ test("Forum Name Step", assert => { assert.ok(!exists('.wizard-field .field-error-description')); assert.ok(!exists('.wizard-step-title')); assert.ok(!exists('.wizard-step-description')); - assert.ok(exists('input.field-email'), "went to the next step"); + + assert.ok(exists('select.field-snack'), "went to the next step"); + assert.ok(exists('.preview-area'), "renders the component field"); + assert.ok(!exists('.wizard-btn.next')); assert.ok(exists('.wizard-btn.done'), 'last step shows a done button'); assert.ok(exists('.wizard-btn.back'), 'shows the back button'); diff --git a/app/assets/javascripts/wizard/test/test_helper.js b/app/assets/javascripts/wizard/test/test_helper.js index 17f51e38b00..caf53122b51 100644 --- a/app/assets/javascripts/wizard/test/test_helper.js +++ b/app/assets/javascripts/wizard/test/test_helper.js @@ -10,6 +10,7 @@ //= require ember-qunit //= require ember-shim //= require wizard-application +//= require wizard-vendor //= require helpers/assertions //= require_tree ./acceptance //= require_tree ./models diff --git a/app/assets/javascripts/wizard/test/wizard-pretender.js.es6 b/app/assets/javascripts/wizard/test/wizard-pretender.js.es6 index 6e0808fc674..546f61fd7d9 100644 --- a/app/assets/javascripts/wizard/test/wizard-pretender.js.es6 +++ b/app/assets/javascripts/wizard/test/wizard-pretender.js.es6 @@ -49,7 +49,10 @@ export default function() { { id: 'second-step', index: 1, - fields: [{ id: 'email', type: 'text', required: true }], + fields: [ + { id: 'snack', type: 'dropdown', required: true }, + { id: 'scheme-preview', type: 'component' } + ], previous: 'hello-world' }] } diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index dfffa573b76..cfb048e5b6c 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -16,6 +16,9 @@ body.wizard { .select { width: 400px; } +.select2-results .select2-highlighted { + background: #ff9; +} .wizard-column { background-color: white; diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index 0c1867f45e7..11c022583b7 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -3,6 +3,32 @@ require_dependency 'distributed_cache' class ColorScheme < ActiveRecord::Base + def self.themes + base_with_hash = {} + base_colors.each do |name, color| + base_with_hash[name] = "##{color}" + end + + [ + { id: 'default', colors: base_with_hash }, + { + id: 'dark', + colors: { + "primary" => '#dddddd', + "secondary" => '#222222', + "tertiary" => '#0f82af', + "quaternary" => '#c14924', + "header_background" => '#111111', + "header_primary" => '#333333', + "highlight" => '#a87137', + "danger" => '#e45735', + "success" => '#1ca551', + "love" => '#fa6c8d' + } + } + ] + end + def self.hex_cache @hex_cache ||= DistributedCache.new("scheme_hex_for_name") end @@ -30,7 +56,7 @@ class ColorScheme < ActiveRecord::Base @mutex.synchronize do return @base_colors if @base_colors @base_colors = {} - read_colors_file.each do |line| + File.readlines(BASE_COLORS_FILE).each do |line| matches = /\$([\w]+):\s*#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})(?:[;]|\s)/.match(line.strip) @base_colors[matches[1]] = matches[2] if matches end @@ -38,10 +64,6 @@ class ColorScheme < ActiveRecord::Base @base_colors end - def self.read_colors_file - File.readlines(BASE_COLORS_FILE) - end - def self.enabled current_version.find_by(enabled: true) end @@ -114,7 +136,6 @@ class ColorScheme < ActiveRecord::Base DiscourseStylesheets.cache.clear end - def dump_hex_cache self.class.hex_cache.clear end diff --git a/app/serializers/wizard_field_serializer.rb b/app/serializers/wizard_field_serializer.rb index 07babfed04d..7f69605eead 100644 --- a/app/serializers/wizard_field_serializer.rb +++ b/app/serializers/wizard_field_serializer.rb @@ -52,7 +52,17 @@ class WizardFieldSerializer < ApplicationSerializer def options object.options.map do |o| - {id: o, label: I18n.t("#{i18n_key}.options.#{o}")} + + result = {id: o, label: I18n.t("#{i18n_key}.options.#{o}")} + + data = object.option_data[o] + if data.present? + as_json = data.dup + as_json.delete(:id) + result[:data] = as_json + end + + result end end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 3548ce27816..c3be14d8a97 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3247,8 +3247,8 @@ en: color_scheme: label: "Color Scheme" options: - default: "Default Scheme" - dark: "Dark Scheme" + default: "Simple" + dark: "Dark" finished: title: "Your Discourse Forum is Ready!" diff --git a/db/migrate/20160906200439_add_via_wizard_to_color_schemes.rb b/db/migrate/20160906200439_add_via_wizard_to_color_schemes.rb new file mode 100644 index 00000000000..40c69d41672 --- /dev/null +++ b/db/migrate/20160906200439_add_via_wizard_to_color_schemes.rb @@ -0,0 +1,5 @@ +class AddViaWizardToColorSchemes < ActiveRecord::Migration + def change + add_column :color_schemes, :via_wizard, :boolean, default: false, null: false + end +end diff --git a/lib/wizard.rb b/lib/wizard.rb index a700f2457ad..7e8d46a00c4 100644 --- a/lib/wizard.rb +++ b/lib/wizard.rb @@ -44,8 +44,9 @@ class Wizard theme = wizard.create_step('colors') scheme = theme.add_field(id: 'color_scheme', type: 'dropdown', required: true) - scheme.add_option('default') - scheme.add_option('dark') + ColorScheme.themes.each {|t| scheme.add_option(t[:id], t) } + + theme.add_field(id: 'scheme_preview', type: 'component') wizard.append_step(theme) finished = wizard.create_step('finished') diff --git a/lib/wizard/field.rb b/lib/wizard/field.rb index 83b6763bd4b..70afc2df88b 100644 --- a/lib/wizard/field.rb +++ b/lib/wizard/field.rb @@ -1,7 +1,7 @@ class Wizard class Field - attr_reader :id, :type, :required, :value, :options + attr_reader :id, :type, :required, :value, :options, :option_data attr_accessor :step def initialize(attrs) @@ -12,10 +12,12 @@ class Wizard @required = !!attrs[:required] @value = attrs[:value] @options = [] + @option_data = {} end - def add_option(id) + def add_option(id, data=nil) @options << id + @option_data[id] = data end end diff --git a/lib/wizard/step_updater.rb b/lib/wizard/step_updater.rb index aaf6208b4b2..543f55eff44 100644 --- a/lib/wizard/step_updater.rb +++ b/lib/wizard/step_updater.rb @@ -23,6 +23,34 @@ class Wizard update_setting(:site_contact_username, fields, :site_contact_username) end + def update_colors(fields) + scheme_name = fields[:color_scheme] + + theme = ColorScheme.themes.find {|s| s[:id] == scheme_name } + + colors = [] + theme[:colors].each do |name, hex| + colors << {name: name, hex: hex[1..-1] } + end + + attrs = { + enabled: true, + name: I18n.t("wizard.step.colors.fields.color_scheme.options.#{scheme_name}"), + colors: colors + } + + scheme = ColorScheme.where(via_wizard: true).first + if scheme.present? + attrs[:colors] = colors + revisor = ColorSchemeRevisor.new(scheme, attrs) + revisor.revise + else + attrs[:via_wizard] = true + scheme = ColorScheme.new(attrs) + scheme.save! + end + end + def success? @errors.blank? end diff --git a/public/images/wizard/discourse-small.png b/public/images/wizard/discourse-small.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc9748d9ff0f9d6b401de48dee4611efe643ed6 GIT binary patch literal 1318 zcmV+>1=;$EP)pUX>tCmzAA?i0HAhTQ#Zeh!kFX z0IF_})`bA7cp)e)0;S>!in|As$Z*Vg_n}{Qb9Ce5R{*PwK)q-X zu2$$PB-Yi{-|wOTtf?4GVLv-Os2n4l9V2)hB5WQa0IZ-NF*;x#A^Eg%fYE;duH`Qu zAONk=3xTfqt7sfLM*yi>{_mv?c%kPfBpO3h>_8&`tAg@oD+HX?`^bXxa5T*vB=A%s zFt}m>t=sf@JpA006kT2VzIy2|BkD6F0Iai6!fh5+ZUCxe&+44?i&rU;WJew$Fe4!1 zUq+-wA@z+;5@u>EZB_!Ly2?=^V8?UXA0+KaBcL%NKB-pmXf&khY+qOBjZ5zk7dF}hpPdc+}HzrAED(icm>KBn<7cr|xG^LYBSPu#}%TPvB z*vR3OdmtN8tRwq}Tmh+h#UX=yB&aFQk#%Q4^eg;m(+ z7&=h~E0j%OETEEkm>>Sm7~OkLXN8;zL{&C{d+**@PDPZ7Q6OO$l@L&^viq4m2D?=0 zxFLKqg#3YOonQX7ItI~+gdv=uB2?S_^pPZFk=d#e8m6_BE=`89Y8;_ZXkB&83~cStm87UT9-}fixdXS4G2y3L z5jO{V!6W)%v6pHFe(h`-br?IP^3sG2u!;>qknu>(i4Jgyzaa!wazPbLnDe;rhVb=a zBFGYFsw)Dt?T8U6?}}IfcqSQ^*mX(NyM#^YK=k&w*equ z&kC7^ze1b^|EFTsfp1kmL!{${0q#WM6F_WMmLZh*<4fLVqC*fjyU3m*mK$>tUQ9x? z6yUHcgaw9Ji-n>*p-3Gf#S+O!d7jV!fOz1@0!t)v7Y^ezg-rm?+r&M_67iqy_xdU9 z2w;zSEmPe*TDJqfK$KG?JR{!#EV1^-L3er^qch#>n-eCmKMQC<7OxS>mz}=#SD=x} ziKiPI6BCp0D&we#o;muPk0pMKcCK%XKe_80--kGjSp|JqvdZT9t6 zYli^%W{WK&rz`*81t>jrg@`h}+|OV07*qoM6N<$f)9&TKmY&$ literal 0 HcmV?d00001 diff --git a/public/images/wizard/trout.png b/public/images/wizard/trout.png new file mode 100644 index 0000000000000000000000000000000000000000..5af72ee42586f8548c12a8d9898b26a7a342970d GIT binary patch literal 8844 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 EA2`J+F#rGn literal 0 HcmV?d00001 diff --git a/spec/components/step_updater_spec.rb b/spec/components/step_updater_spec.rb index df982600466..336c9fe3336 100644 --- a/spec/components/step_updater_spec.rb +++ b/spec/components/step_updater_spec.rb @@ -21,7 +21,7 @@ describe Wizard::StepUpdater do contact_url: 'http://example.com/custom-contact-url', site_contact_username: user.username) - expect(updater.success?).to eq(true) + expect(updater).to be_success expect(SiteSetting.contact_email).to eq("eviltrout@example.com") expect(SiteSetting.contact_url).to eq("http://example.com/custom-contact-url") expect(SiteSetting.site_contact_username).to eq(user.username) @@ -30,9 +30,39 @@ describe Wizard::StepUpdater do it "doesn't update when there are errors" do updater.update(contact_email: 'not-an-email', site_contact_username: 'not-a-username') - expect(updater.success?).to eq(false) + expect(updater).to be_success expect(updater.errors).to be_present end end + context "colors step" do + let(:updater) { Wizard::StepUpdater.new(user, 'colors') } + + context "with an existing color scheme" do + let!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) } + + it "updates the scheme" do + updater.update(color_scheme: 'dark') + expect(updater.success?).to eq(true) + + color_scheme.reload + expect(color_scheme).to be_enabled + + end + end + + context "without an existing scheme" do + + it "creates the scheme" do + updater.update(color_scheme: 'dark') + expect(updater.success?).to eq(true) + + color_scheme = ColorScheme.where(via_wizard: true).first + expect(color_scheme).to be_present + expect(color_scheme).to be_enabled + expect(color_scheme.colors).to be_present + end + end + end + end