diff --git a/app/assets/javascripts/wizard/components/homepage-preview.js.es6 b/app/assets/javascripts/wizard/components/homepage-preview.js.es6 new file mode 100644 index 00000000000..7fad8f37862 --- /dev/null +++ b/app/assets/javascripts/wizard/components/homepage-preview.js.es6 @@ -0,0 +1,188 @@ +import { observes } from 'ember-addons/ember-computed-decorators'; +import { createPreviewComponent, LOREM, darkLightDiff } from 'wizard/lib/preview'; + +export default createPreviewComponent(659, 320, { + logo: null, + avatar: null, + + @observes('step.fieldsById.homepage_style.value') + styleChanged() { + this.triggerRepaint(); + }, + + images() { + return { logo: this.get('wizard').getLogoUrl(), avatar: '/images/wizard/trout.png' }; + }, + + paint(ctx, colors, width, height) { + this.drawFullHeader(colors); + this.drawPills(colors, height * 0.15); + + if (this.get('step.fieldsById.homepage_style.value') === "latest") { + this.renderLatest(ctx, colors, width, height); + } else { + this.renderCategories(ctx, colors, width, height); + } + }, + + renderCategories(ctx, colors, width, height) { + const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50); + const margin = height * 0.03; + const bodyFontSize = height / 440.0; + + const drawLine = (x, y) => { + ctx.beginPath(); + ctx.strokeStyle = darkLightDiff(colors.primary, colors.secondary, 90, -75); + ctx.moveTo(margin + x, y); + ctx.lineTo(margin + x + ((width * 0.9) / 2), y); + ctx.stroke(); + }; + + const cols = [0.025, 0.4, 0.53, 0.58, 0.94].map(c => c * width); + + const headingY = height * 0.33; + ctx.font = `${bodyFontSize * 0.9}em 'Arial'`; + ctx.fillStyle = textColor; + ctx.fillText("Category", cols[0], headingY); + ctx.fillText("Topics", cols[1], headingY); + ctx.fillText("Latest", cols[2], headingY); + + let y = headingY + (bodyFontSize * 12); + ctx.lineWidth = 2; + drawLine(0, y); + drawLine(width / 2, y); + + + + const categoryHeight = height / 6; + + const titles = this.getTitles(); + + // Categories + this.categories().forEach(category => { + const textPos = y + (categoryHeight * 0.35); + ctx.font = `Bold ${bodyFontSize * 1.1}em 'Arial'`; + ctx.fillStyle = colors.primary; + ctx.fillText(category.name, cols[0], textPos); + + ctx.font = `${bodyFontSize * 0.8}em 'Arial'`; + ctx.fillStyle = textColor; + ctx.fillText(titles[0], cols[0] - (margin * 0.25), textPos + (categoryHeight * 0.36)); + + ctx.beginPath(); + ctx.moveTo(margin, y); + ctx.strokeStyle = category.color; + ctx.lineWidth = 3.5; + ctx.lineTo(margin, y + categoryHeight); + ctx.stroke(); + + y += categoryHeight; + ctx.lineWidth = 1; + drawLine(0, y); + }); + + // Categories - Latest Topics + const topicHeight = height / 8; + const avatarSize = topicHeight * 0.7; + y = headingY + (bodyFontSize * 12); + ctx.lineWidth = 1; + ctx.fillStyle = textColor; + + titles.forEach(title => { + const category = this.categories()[0]; + ctx.font = `${bodyFontSize}em 'Arial'`; + const textPos = y + (topicHeight * 0.45); + ctx.fillStyle = textColor; + ctx.drawImage(this.avatar, cols[2], y + (margin * 0.6), avatarSize, avatarSize); + ctx.fillText(title, cols[3], textPos); + + ctx.font = `Bold ${bodyFontSize}em 'Arial'`; + ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[4], textPos); + ctx.font = `${bodyFontSize}em 'Arial'`; + ctx.fillText(`1h`, cols[4], textPos + (topicHeight * 0.4)); + + ctx.beginPath(); + ctx.fillStyle = category.color; + const badgeSize = topicHeight * 0.1; + ctx.font = `Bold ${bodyFontSize * 0.5}em 'Arial'`; + ctx.rect(cols[3] + (margin * 0.5), y + topicHeight * 0.65, badgeSize, badgeSize); + ctx.fill(); + + ctx.fillStyle = colors.primary; + ctx.fillText(category.name, cols[3] + (badgeSize * 3), y + (topicHeight * 0.76)); + y += topicHeight; + + drawLine(width / 2, y); + }); + + }, + + getTitles() { + return LOREM.split(".").slice(0, 7).map(t => t.substring(0, 40)); + }, + + renderLatest(ctx, colors, width, height) { + const rowHeight = height / 10.0; + const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50); + const bodyFontSize = height / 440.0; + + ctx.font = `${bodyFontSize}em 'Arial'`; + + const margin = height * 0.03; + + const drawLine = y => { + ctx.beginPath(); + ctx.strokeStyle = darkLightDiff(colors.primary, colors.secondary, 90, -75); + ctx.moveTo(margin, y); + ctx.lineTo(width - margin, y); + ctx.stroke(); + }; + + const cols = [0.02, 0.5, 0.65, 0.8, 0.87, 0.93].map(c => c * width); + + // Headings + const headingY = height * 0.33; + + ctx.fillStyle = textColor; + ctx.font = `${bodyFontSize * 0.9}em 'Arial'`; + ctx.fillText("Topic", cols[0], headingY); + ctx.fillText("Category", cols[1], headingY); + ctx.fillText("Users", cols[2], headingY); + ctx.fillText("Replies", cols[3], headingY); + ctx.fillText("Views", cols[4], headingY); + ctx.fillText("Activity", cols[5], headingY); + + // Topics + let y = headingY + rowHeight / 2.6; + ctx.lineWidth = 2; + drawLine(y); + + ctx.font = `${bodyFontSize}em 'Arial'`; + ctx.lineWidth = 1; + this.getTitles().forEach(title => { + const textPos = y + (rowHeight * 0.7); + ctx.fillStyle = textColor; + ctx.fillText(title, cols[0], textPos); + + const category = this.categories()[0]; + ctx.beginPath(); + ctx.fillStyle = category.color; + const badgeSize = rowHeight * 0.2; + ctx.font = `Bold ${bodyFontSize * 0.5}em 'Arial'`; + ctx.rect(cols[1], y + rowHeight * 0.5, badgeSize, badgeSize); + ctx.fill(); + + ctx.fillStyle = colors.primary; + ctx.fillText(category.name, cols[1] + (badgeSize * 1.5), y + (rowHeight * 0.65)); + ctx.drawImage(this.avatar, cols[2], y + rowHeight * 0.3, rowHeight * 0.5, rowHeight * 0.5); + + ctx.fillStyle = textColor; + ctx.font = `${bodyFontSize}em 'Arial'`; + for (let j=3; j<=5; j++) { + ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[j] + margin, y + (rowHeight * 0.7)); + } + drawLine(y + (rowHeight * 1)); + y += rowHeight; + }); + } +}); diff --git a/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon-url.js.es6 b/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon-url.js.es6 index b3ea4c14dbe..18c91586ddf 100644 --- a/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon-url.js.es6 +++ b/app/assets/javascripts/wizard/components/image-preview-apple-touch-icon-url.js.es6 @@ -1,9 +1,5 @@ import { observes } from 'ember-addons/ember-computed-decorators'; - -import { - createPreviewComponent, - loadImage, -} from 'wizard/lib/preview'; +import { createPreviewComponent } from 'wizard/lib/preview'; export default createPreviewComponent(325, 125, { ios: null, @@ -14,18 +10,8 @@ export default createPreviewComponent(325, 125, { this.reload(); }, - load() { - return Ember.RSVP.Promise.all([ - loadImage('/images/wizard/apple-mask.png'), - loadImage(this.get('field.value')) - ]).then(result => { - this.ios = result[0]; - this.image = result[1]; - }); - - return loadImage(this.get('field.value')).then(image => { - this.image = image; - }); + images() { + return { ios: '/images/wizard/apple-mask.png', image: this.get('field.value') }; }, paint(ctx, colors, width, height) { diff --git a/app/assets/javascripts/wizard/components/image-preview-favicon-url.js.es6 b/app/assets/javascripts/wizard/components/image-preview-favicon-url.js.es6 index 2beeb0637f1..2e782820c82 100644 --- a/app/assets/javascripts/wizard/components/image-preview-favicon-url.js.es6 +++ b/app/assets/javascripts/wizard/components/image-preview-favicon-url.js.es6 @@ -1,9 +1,6 @@ import { observes } from 'ember-addons/ember-computed-decorators'; -import { - createPreviewComponent, - loadImage, -} from 'wizard/lib/preview'; +import { createPreviewComponent, } from 'wizard/lib/preview'; export default createPreviewComponent(371, 124, { tab: null, @@ -14,18 +11,8 @@ export default createPreviewComponent(371, 124, { this.reload(); }, - load() { - return Ember.RSVP.Promise.all([ - loadImage('/images/wizard/tab.png'), - loadImage(this.get('field.value')) - ]).then(result => { - this.tab = result[0]; - this.image = result[1]; - }); - - return loadImage(this.get('field.value')).then(image => { - this.image = image; - }); + images() { + return { tab: "/images/wizard/tab.png", image: this.get('field.value') }; }, paint(ctx, colors, width, height) { diff --git a/app/assets/javascripts/wizard/components/image-preview-logo-small-url.js.es6 b/app/assets/javascripts/wizard/components/image-preview-logo-small-url.js.es6 index 56c06f0a9cb..aa70b2c652b 100644 --- a/app/assets/javascripts/wizard/components/image-preview-logo-small-url.js.es6 +++ b/app/assets/javascripts/wizard/components/image-preview-logo-small-url.js.es6 @@ -2,7 +2,6 @@ import { observes } from 'ember-addons/ember-computed-decorators'; import { createPreviewComponent, - loadImage, drawHeader, LOREM } from 'wizard/lib/preview'; @@ -15,10 +14,8 @@ export default createPreviewComponent(375, 100, { this.reload(); }, - load() { - return loadImage(this.get('field.value')).then(image => { - this.image = image; - }); + images() { + return { image: this.get('field.value') }; }, paint(ctx, colors, width, height) { @@ -46,16 +43,16 @@ export default createPreviewComponent(375, 100, { const title = LOREM.substring(0, 27); ctx.fillText(title, headerMargin + imageWidth, headerHeight - (fontSize * 1.1)); + const category = this.categories()[0]; const badgeSize = height / 13.0; ctx.beginPath(); - ctx.fillStyle = colors.primary; + ctx.fillStyle = category.color; ctx.rect(afterLogo, (headerHeight * 0.70), badgeSize, badgeSize); ctx.fill(); ctx.font = `Bold ${badgeSize * 1.2}px 'Arial'`; ctx.fillStyle = colors.primary; - const category = "consectetur"; - ctx.fillText(category, afterLogo + (badgeSize * 1.5), headerHeight * 0.7 + (badgeSize * 0.9)); + ctx.fillText(category.name, afterLogo + (badgeSize * 1.5), headerHeight * 0.7 + (badgeSize * 0.9)); const LINE_HEIGHT = 12; ctx.font = `${LINE_HEIGHT}px 'Arial'`; diff --git a/app/assets/javascripts/wizard/components/image-preview-logo-url.js.es6 b/app/assets/javascripts/wizard/components/image-preview-logo-url.js.es6 index 9bd065d0c99..b984c8e1cb9 100644 --- a/app/assets/javascripts/wizard/components/image-preview-logo-url.js.es6 +++ b/app/assets/javascripts/wizard/components/image-preview-logo-url.js.es6 @@ -1,11 +1,6 @@ import { observes } from 'ember-addons/ember-computed-decorators'; -import { - createPreviewComponent, - loadImage, - drawHeader, - darkLightDiff -} from 'wizard/lib/preview'; +import { createPreviewComponent, drawHeader } from 'wizard/lib/preview'; export default createPreviewComponent(400, 100, { image: null, @@ -15,10 +10,8 @@ export default createPreviewComponent(400, 100, { this.reload(); }, - load() { - return loadImage(this.get('field.value')).then(image => { - this.image = image; - }); + images() { + return { image: this.get('field.value') }; }, paint(ctx, colors, width, height) { @@ -33,44 +26,7 @@ export default createPreviewComponent(400, 100, { const ratio = imageHeight / image.height; ctx.drawImage(image, headerMargin, headerMargin, image.width * ratio, imageHeight); - const categoriesSize = width / 3.8; - const badgeHeight = categoriesSize * 0.25; - - ctx.beginPath(); - ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 90, -65); - ctx.rect(headerMargin, headerHeight + headerMargin, categoriesSize, badgeHeight); - ctx.fill(); - - const fontSize = Math.round(badgeHeight * 0.5); - ctx.font = `${fontSize}px 'Arial'`; - ctx.fillStyle = colors.primary; - ctx.fillText("all categories", headerMargin * 1.5, headerHeight + (headerMargin * 1.5) + fontSize); - - ctx.font = "0.9em 'FontAwesome'"; - ctx.fillStyle = colors.primary; - ctx.fillText("\uf0da", categoriesSize - (headerMargin / 4), headerHeight + (headerMargin * 1.6) + fontSize); - - // pills - ctx.beginPath(); - ctx.fillStyle = colors.quaternary; - ctx.rect((headerMargin * 2)+ categoriesSize, headerHeight + headerMargin, categoriesSize * 0.55, badgeHeight); - ctx.fill(); - - ctx.font = `${fontSize}px 'Arial'`; - ctx.fillStyle = colors.secondary; - let x = (headerMargin * 3.0) + categoriesSize; - - ctx.fillText("Latest", x, headerHeight + (headerMargin * 1.5) + fontSize); - - ctx.fillStyle = colors.primary; - x += categoriesSize * 0.6; - ctx.fillText("New", x, headerHeight + (headerMargin * 1.5) + fontSize); - - x += categoriesSize * 0.4; - ctx.fillText("Unread", x, headerHeight + (headerMargin * 1.5) + fontSize); - - x += categoriesSize * 0.6; - ctx.fillText("Top", x, headerHeight + (headerMargin * 1.5) + fontSize); + this.drawPills(colors, height / 2); } }); diff --git a/app/assets/javascripts/wizard/components/theme-preview.js.es6 b/app/assets/javascripts/wizard/components/theme-preview.js.es6 index a0c090bc5b2..232be2f0e14 100644 --- a/app/assets/javascripts/wizard/components/theme-preview.js.es6 +++ b/app/assets/javascripts/wizard/components/theme-preview.js.es6 @@ -2,10 +2,8 @@ import { observes } from 'ember-addons/ember-computed-decorators'; import { createPreviewComponent, - loadImage, darkLightDiff, chooseBrighter, - drawHeader, LOREM } from 'wizard/lib/preview'; @@ -18,45 +16,27 @@ export default createPreviewComponent(659, 320, { this.triggerRepaint(); }, - load() { - return 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]; - }); + images() { + return { logo: this.get('wizard').getLogoUrl(), avatar: '/images/wizard/trout.png' }; }, paint(ctx, colors, width, height) { const headerHeight = height * 0.15; - drawHeader(ctx, colors, width, headerHeight); + this.drawFullHeader(colors); const margin = width * 0.02; const avatarSize = height * 0.1; const lineHeight = height / 19.0; - // 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); - - const headerFontSize = headerHeight / 44; - - ctx.font = `${headerFontSize}em 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); + const titleFontSize = headerHeight / 44; + ctx.beginPath(); ctx.fillStyle = colors.primary; - ctx.font = `bold ${headerFontSize}em 'Arial'`; + ctx.font = `bold ${titleFontSize}em 'Arial'`; ctx.fillText("Welcome to Discourse", margin, (height * 0.25)); const bodyFontSize = height / 440.0; diff --git a/app/assets/javascripts/wizard/lib/preview.js.es6 b/app/assets/javascripts/wizard/lib/preview.js.es6 index 38957583aea..c75c5a4a9e5 100644 --- a/app/assets/javascripts/wizard/lib/preview.js.es6 +++ b/app/assets/javascripts/wizard/lib/preview.js.es6 @@ -28,8 +28,20 @@ export function createPreviewComponent(width, height, obj) { this.reload(); }, + images() { }, + + loadImages() { + const images = this.images(); + if (images) { + return Ember.RSVP.Promise.all(Object.keys(images).map(id => { + return loadImage(images[id]).then(img => this[id] = img); + })); + } + return Ember.RSVP.Promise.resolve(); + }, + reload() { - this.load().then(() => { + this.loadImages().then(() => { this.loaded = true; this.triggerRepaint(); }); @@ -57,14 +69,87 @@ export function createPreviewComponent(width, height, obj) { ctx.strokeStyle='rgba(0, 0, 0, 0.2)'; ctx.rect(0, 0, width, height); ctx.stroke(); + }, + + categories() { + return [{name: 'consecteteur', color: '#652D90'}, {name: 'ultrices', color: '#3AB54A'}]; + }, + + drawFullHeader(colors) { + const { ctx } = this; + + const headerHeight = height * 0.15; + drawHeader(ctx, colors, width, headerHeight); + + 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); + + const headerFontSize = headerHeight / 44; + + ctx.font = `${headerFontSize}em FontAwesome`; + ctx.fillText("\uf0c9", width - (avatarSize * 2) - (headerMargin * 0.5), avatarSize); + ctx.fillText("\uf002", width - (avatarSize * 3) - (headerMargin * 0.5), avatarSize); + }, + + drawPills(colors, headerHeight) { + const { ctx } = this; + + const categoriesSize = headerHeight * 2; + const badgeHeight = categoriesSize * 0.25; + const headerMargin = headerHeight * 0.2; + + ctx.beginPath(); + ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 90, -65); + ctx.rect(headerMargin, headerHeight + headerMargin, categoriesSize, badgeHeight); + ctx.fill(); + + const fontSize = Math.round(badgeHeight * 0.5); + ctx.font = `${fontSize}px 'Arial'`; + ctx.fillStyle = colors.primary; + ctx.fillText("all categories", headerMargin * 1.5, headerHeight + (headerMargin * 1.42) + fontSize); + + ctx.font = "0.9em 'FontAwesome'"; + ctx.fillStyle = colors.primary; + ctx.fillText("\uf0da", categoriesSize - (headerMargin / 4), headerHeight + (headerMargin * 1.6) + fontSize); + + // pills + ctx.beginPath(); + ctx.fillStyle = colors.quaternary; + ctx.rect((headerMargin * 2)+ categoriesSize, headerHeight + headerMargin, categoriesSize * 0.55, badgeHeight); + ctx.fill(); + + ctx.font = `${fontSize}px 'Arial'`; + ctx.fillStyle = colors.secondary; + let x = (headerMargin * 3.0) + categoriesSize; + + ctx.fillText("Latest", x - (headerMargin * 0.1), headerHeight + (headerMargin * 1.5) + fontSize); + + ctx.fillStyle = colors.primary; + x += categoriesSize * 0.6; + ctx.fillText("New", x, headerHeight + (headerMargin * 1.5) + fontSize); + + x += categoriesSize * 0.4; + ctx.fillText("Unread", x, headerHeight + (headerMargin * 1.5) + fontSize); + + x += categoriesSize * 0.6; + ctx.fillText("Top", x, headerHeight + (headerMargin * 1.5) + fontSize); } + }, obj); } -export function loadImage(src) { +function loadImage(src) { const img = new Image(); img.src = src; - return new Ember.RSVP.Promise(resolve => img.onload = () => resolve(img)); }; @@ -158,10 +243,10 @@ export function darkLightDiff(adjusted, comparison, lightness, darkness) { } -export function drawHeader(ctx, colors, width, height) { +export function drawHeader(ctx, colors, width, headerHeight) { ctx.save(); ctx.beginPath(); - ctx.rect(0, 0, width, height); + ctx.rect(0, 0, width, headerHeight); ctx.fillStyle = colors.header_background; ctx.shadowColor = "rgba(0, 0, 0, 0.25)"; ctx.shadowBlur = 2; diff --git a/app/assets/javascripts/wizard/models/wizard.js.es6 b/app/assets/javascripts/wizard/models/wizard.js.es6 index 64b28374a5d..68a4f56c6fd 100644 --- a/app/assets/javascripts/wizard/models/wizard.js.es6 +++ b/app/assets/javascripts/wizard/models/wizard.js.es6 @@ -13,6 +13,13 @@ const Wizard = Ember.Object.extend({ return titleStep.get('fieldsById.title.value'); }, + getLogoUrl() { + const logoStep = this.get('steps').findProperty('id', 'logos'); + if (!logoStep) { return; } + return logoStep.get('fieldsById.logo_url.value'); + + }, + // A bit clunky, but get the current colors from the appropriate step getCurrentColors() { const colorStep = this.get('steps').findProperty('id', 'colors'); diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index e17290baddd..159bf193150 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3329,6 +3329,18 @@ en: label: "Large Icon" description: "Icon used to represent your site on mobile devices. Recommended size is 144px by 144px." + homepage: + description: "We recommend showing the Latest topics by default, but some people prefer to show Categories." + title: "Homepage Style" + + fields: + homepage_style: + choices: + latest: + label: "Latest Topics" + categories: + label: "Categories" + emoji: title: "Emoji" description: "Which Emoji style do you prefer for your community? Staff members can also add more custom Emoji later via Admin, Customize, Emoji." diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb index f1bd55404aa..7726e36976f 100644 --- a/lib/wizard/builder.rb +++ b/lib/wizard/builder.rb @@ -169,6 +169,19 @@ class Wizard end end + @wizard.append_step('homepage') do |step| + style = step.add_field(id: 'homepage_style', type: 'dropdown', required: true) + style.add_choice('latest') + style.add_choice('categories') + step.add_field(id: 'homepage_preview', type: 'component') + + step.on_update do |updater| + top_menu = "latest|new|unread|top|categories" + top_menu = "categories|latest|new|unread|top" if updater.fields[:homepage_style] == 'categories' + updater.update_setting(:top_menu, top_menu) + end + end + @wizard.append_step('emoji') do |step| sets = step.add_field({ id: 'emoji_set', diff --git a/spec/components/step_updater_spec.rb b/spec/components/step_updater_spec.rb index 564fe9d36ee..eed8e7f7f5b 100644 --- a/spec/components/step_updater_spec.rb +++ b/spec/components/step_updater_spec.rb @@ -215,6 +215,21 @@ describe Wizard::StepUpdater do end end + context "homepage step" do + it "updates the fields correctly" do + updater = wizard.create_updater('homepage', homepage_style: "categories") + updater.update + + expect(updater).to be_success + expect(wizard.completed_steps?('homepage')).to eq(true) + expect(SiteSetting.top_menu).to eq('categories|latest|new|unread|top') + + updater = wizard.create_updater('homepage', homepage_style: "latest") + updater.update + expect(updater).to be_success + expect(SiteSetting.top_menu).to eq('latest|new|unread|top|categories') + end + end context "invites step" do let(:invites) {