Merge branch 'main' into bugfix/inbox-sharing

This commit is contained in:
Andreas Nedbal 2023-10-19 23:44:57 +02:00 committed by GitHub
commit 6e8f8bcc67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 299 additions and 204 deletions

View File

@ -1,4 +1,4 @@
FROM ruby:3.1
FROM ruby:3.2
USER root

1
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1 @@
71894d6c4987547533606258447b576ecb604c2b

View File

@ -46,7 +46,10 @@ Metrics/ClassLength:
Enabled: false
Metrics/CyclomaticComplexity:
Severity: refactor
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Metrics/ModuleLength:
Enabled: false

View File

@ -1 +1 @@
3.1.2
3.2.2

View File

@ -8,8 +8,8 @@ LABEL org.opencontainers.image.vendor="The Retrospring team"
LABEL org.opencontainers.image.url="https://github.com/Retrospring/retrospring"
ARG RETROSPRING_VERSION=2023.0131.1
ARG RUBY_VERSION=3.1.2
ARG RUBY_INSTALL_VERSION=0.9.0
ARG RUBY_VERSION=3.2.2
ARG RUBY_INSTALL_VERSION=0.9.2
ARG BUNDLER_VERSION=2.3.18
ENV RAILS_ENV=production

View File

@ -470,7 +470,7 @@ GEM
timeout (0.4.0)
tldv (0.1.0)
tldv-data (~> 1.0)
tldv-data (1.0.2023031000)
tldv-data (1.0.2023080900)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
@ -582,4 +582,4 @@ DEPENDENCIES
twitter-text
BUNDLED WITH
2.3.18
2.4.21

View File

@ -1,2 +1,4 @@
# frozen_string_literal: true
module AjaxHelper
end

View File

@ -4,13 +4,13 @@ module ApplicationHelper::GraphMethods
# Creates <meta> tags for OpenGraph properties from a hash
# @param values [Hash]
def opengraph_meta_tags(values)
safe_join(values.map { |name, content| tag.meta(property: name, content: content) }, "\n")
safe_join(values.map { |name, content| tag.meta(property: name, content:) }, "\n")
end
# Creates <meta> tags from a hash
# @param values [Hash]
def meta_tags(values)
safe_join(values.map { |name, content| tag.meta(name: name, content: content) }, "\n")
safe_join(values.map { |name, content| tag.meta(name:, content:) }, "\n")
end
# @param user [User]
@ -22,7 +22,7 @@ module ApplicationHelper::GraphMethods
"og:url": user_url(user),
"og:description": user.profile.description,
"og:site_name": APP_CONFIG["site_name"],
"profile:username": user.screen_name
"profile:username": user.screen_name,
})
end
@ -33,7 +33,7 @@ module ApplicationHelper::GraphMethods
"twitter:site": "@retrospring",
"twitter:title": user.profile.motivation_header.presence || "Ask me anything!",
"twitter:description": "Ask #{user.profile.safe_name} anything on Retrospring",
"twitter:image": full_profile_picture_url(user)
"twitter:image": full_profile_picture_url(user),
})
end
@ -45,7 +45,7 @@ module ApplicationHelper::GraphMethods
"og:image": full_profile_picture_url(answer.user),
"og:url": answer_url(answer.user.screen_name, answer.id),
"og:description": answer.content,
"og:site_name": APP_CONFIG["site_name"]
"og:site_name": APP_CONFIG["site_name"],
})
end

View File

@ -28,7 +28,7 @@ module ApplicationHelper::TitleMethods
def question_title(question)
context_user = question.answers&.first&.user if question.direct
name = user_screen_name question.user,
context_user: context_user,
context_user:,
author_identifier: question.author_is_anonymous ? question.author_identifier : nil,
url: false
generate_title name, "asked", question.content

View File

@ -25,24 +25,24 @@ module BootstrapHelper
"#{content_tag(:i, '', class: "fa fa-#{options[:icon]}")} #{body}"
end
end
if options[:badge].present? || options.dig(:badge_attr, :data)&.has_key?(:controller)
if options[:badge].present? || options.dig(:badge_attr, :data)&.key?(:controller)
badge_class = [
"badge",
("badge-#{options[:badge_color]}" unless options[:badge_color].nil?),
("badge-pill" if options[:badge_pill])
].compact.join(" ")
body += " #{content_tag(:span, options[:badge], class: badge_class, **options[:badge_attr])}".html_safe
body += " #{content_tag(:span, options[:badge], class: badge_class, **options[:badge_attr])}".html_safe # rubocop:disable Rails/OutputSafety
end
content_tag(:li, link_to(body.html_safe, path, class: "nav-link", data: { hotkey: options[:hotkey] }), class: classes, id: options[:id])
content_tag(:li, link_to(body.html_safe, path, class: "nav-link", data: { hotkey: options[:hotkey] }), class: classes, id: options[:id]) # rubocop:disable Rails/OutputSafety
end
def list_group_item(body, path, options = {})
options = {
badge: nil,
badge_color: nil,
class: ""
class: "",
}.merge(options)
classes = [
@ -54,13 +54,18 @@ module BootstrapHelper
unless options[:badge].nil? || (options[:badge]).zero?
# TODO: make this prettier?
body << " #{
content_tag(:span, options[:badge], class: "badge#{
badge = content_tag(:span, options[:badge], class: "badge#{
" badge-#{options[:badge_color]}" unless options[:badge_color].nil?
}")}"
}",)
end
content_tag(:a, body.html_safe, href: path, class: classes)
html = if badge
"#{body} #{badge}"
else
body
end
content_tag(:a, html.html_safe, href: path, class: classes) # rubocop:disable Rails/OutputSafety
end
def tooltip(body, tooltip_content, placement = "bottom")

View File

@ -8,7 +8,7 @@ module FeedbackHelper
avatarURL: current_user.profile_picture.url(:large),
name: current_user.screen_name,
id: current_user.id,
email: current_user.email
email: current_user.email,
}
JWT.encode(user_data, APP_CONFIG.dig("canny", "sso"))

View File

@ -30,7 +30,7 @@ module MarkdownHelper
def raw_markdown(content)
renderer = Redcarpet::Render::HTML.new(**MARKDOWN_RENDERER_OPTS)
md = Redcarpet::Markdown.new(renderer, **MARKDOWN_OPTS)
raw md.render content
raw md.render content # rubocop:disable Rails/OutputSafety
end
def get_markdown(path, relative_to = Rails.root)

View File

@ -15,7 +15,7 @@ module SocialHelper::TelegramMethods
id: answer.id,
username: answer.user.screen_name,
host: APP_CONFIG["hostname"],
protocol: (APP_CONFIG["https"] ? :https : :http)
protocol: (APP_CONFIG["https"] ? :https : :http),
)
%(https://t.me/share/url?url=#{CGI.escape(url)}&text=#{CGI.escape(telegram_text(answer))})

View File

@ -1,4 +1,6 @@
require 'cgi'
# frozen_string_literal: true
require "cgi"
module SocialHelper::TumblrMethods
def tumblr_title(answer)
@ -13,10 +15,10 @@ module SocialHelper::TumblrMethods
def tumblr_body(answer)
answer_url = answer_url(
id: answer.id,
username: answer.user.screen_name,
host: APP_CONFIG['hostname'],
protocol: (APP_CONFIG['https'] ? :https : :http)
id: answer.id,
username: answer.user.screen_name,
host: APP_CONFIG["hostname"],
protocol: (APP_CONFIG["https"] ? :https : :http),
)
"#{answer.content}\n\n[Smile or comment on the answer here](#{answer_url})"
@ -24,10 +26,10 @@ module SocialHelper::TumblrMethods
def tumblr_share_url(answer)
answer_url = answer_url(
id: answer.id,
id: answer.id,
username: answer.user.screen_name,
host: APP_CONFIG['hostname'],
protocol: (APP_CONFIG['https'] ? :https : :http)
host: APP_CONFIG["hostname"],
protocol: (APP_CONFIG["https"] ? :https : :http),
)
"https://www.tumblr.com/widgets/share/tool?shareSource=legacy&posttype=text&title=#{CGI.escape(tumblr_title(answer))}&url=#{CGI.escape(answer_url)}&caption=&content=#{CGI.escape(tumblr_body(answer))}"

View File

@ -1,14 +1,16 @@
require 'cgi'
# frozen_string_literal: true
require "cgi"
module SocialHelper::TwitterMethods
include MarkdownHelper
def prepare_tweet(answer, post_tag = nil, omit_url = false)
question_content = twitter_markdown answer.question.content.gsub(/\@(\w+)/, '\1')
question_content = twitter_markdown answer.question.content.gsub(/@(\w+)/, '\1')
original_question_length = question_content.length
answer_content = twitter_markdown answer.content
original_answer_length = answer_content.length
unless omit_url
answer_url = answer_url(
id: answer.id,
@ -18,7 +20,7 @@ module SocialHelper::TwitterMethods
)
end
parsed_tweet = { :valid => false }
parsed_tweet = { valid: false }
tweet_text = ""
until parsed_tweet[:valid]
@ -26,14 +28,14 @@ module SocialHelper::TwitterMethods
shortened_answer = "#{answer_content[0..123]}#{'…' if original_answer_length > [124, answer_content.length].min}"
components = [
shortened_question,
'—',
"",
shortened_answer,
post_tag,
answer_url
]
tweet_text = components.compact.join(' ')
tweet_text = components.compact.join(" ")
parsed_tweet = Twitter::TwitterText::Validation::parse_tweet(tweet_text)
parsed_tweet = Twitter::TwitterText::Validation.parse_tweet(tweet_text)
question_content = question_content[0..-2]
answer_content = answer_content[0..-2]

View File

@ -25,7 +25,7 @@ module ThemeHelper
"input_color" => "input-bg",
"input_text" => "input-text",
"input_placeholder" => "input-placeholder",
"muted_text" => "muted-text"
"muted_text" => "muted-text",
}.freeze
def render_theme

View File

@ -14,15 +14,16 @@ class Inbox < ApplicationRecord
end
after_create do
user.touch(:inbox_updated_at)
user.touch(:inbox_updated_at) # rubocop:disable Rails/SkipsModelValidations
end
after_update do
user.touch(:inbox_updated_at)
user.touch(:inbox_updated_at) # rubocop:disable Rails/SkipsModelValidations
end
after_destroy do
user.touch(:inbox_updated_at)
# user might not exist at this point (account deleted, records are cleaned up async)
user&.touch(:inbox_updated_at) # rubocop:disable Rails/SkipsModelValidations
end
def answer(answer_content, user)
@ -49,7 +50,7 @@ class Inbox < ApplicationRecord
user.profile.anon_display_name || APP_CONFIG["anonymous_name"]
else
question.user.profile.safe_name
end
end,
),
icon: notification_icon,
body: question.content.truncate(Question::SHORT_QUESTION_MAX_LENGTH),

View File

@ -5,15 +5,16 @@ class Notification < ApplicationRecord
belongs_to :target, polymorphic: true
after_create do
recipient.touch(:notifications_updated_at)
recipient.touch(:notifications_updated_at) # rubocop:disable Rails/SkipsModelValidations
end
after_update do
recipient.touch(:notifications_updated_at)
recipient.touch(:notifications_updated_at) # rubocop:disable Rails/SkipsModelValidations
end
after_destroy do
recipient.touch(:notifications_updated_at)
# recipient might not exist at this point (account deleted, records are cleaned up async)
recipient&.touch(:notifications_updated_at) # rubocop:disable Rails/SkipsModelValidations
end
class << self

View File

@ -75,6 +75,7 @@ Rails.application.configure do
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
# config.file_watcher = ActiveSupport::EventedFileUpdateChecker
config.hosts += ENV["EXTRA_HOSTS"].split(':') if ENV["EXTRA_HOSTS"].present?
end
# For better_errors to work inside Docker we need

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
class IncludeTypeInRelationshipUniqueConstraint < ActiveRecord::Migration[6.1]
def change
change_table :relationships do |t|
t.remove_index(%i[source_id target_id])
t.index(%i[source_id target_id type], unique: true)
end
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2023_05_26_181715) do
ActiveRecord::Schema.define(version: 2023_10_18_172518) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -159,7 +159,7 @@ ActiveRecord::Schema.define(version: 2023_05_26_181715) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "type", null: false
t.index ["source_id", "target_id"], name: "index_relationships_on_source_id_and_target_id", unique: true
t.index ["source_id", "target_id", "type"], name: "index_relationships_on_source_id_and_target_id_and_type", unique: true
t.index ["source_id"], name: "index_relationships_on_source_id"
t.index ["target_id"], name: "index_relationships_on_target_id"
t.index ["type"], name: "index_relationships_on_type"

View File

@ -17,9 +17,9 @@ module Retrospring
def month = 10
def day = 17
def day = 19
def patch = 1
def patch = 0
def suffix = ""

View File

@ -1,20 +1,22 @@
# frozen_string_literal: true
require 'rails_helper'
require "rails_helper"
describe ApplicationHelper::GraphMethods, :type => :helper do
describe ApplicationHelper::GraphMethods, type: :helper do
describe "#user_opengraph" do
context "sample user" do
let(:user) { FactoryBot.create(:user,
profile: { display_name: 'Cunes',
description: 'A bunch of raccoons in a trenchcoat.' },
screen_name: 'raccoons') }
let(:user) do
FactoryBot.create(:user,
profile: { display_name: "Cunes",
description: "A bunch of raccoons in a trenchcoat.", },
screen_name: "raccoons",)
end
subject { user_opengraph(user) }
it 'should generate a matching OpenGraph structure for a user' do
allow(APP_CONFIG).to receive(:[]).with('site_name').and_return('pineapplespring')
expect(subject).to eq(<<~EOS.chomp)
it "should generate a matching OpenGraph structure for a user" do
allow(APP_CONFIG).to receive(:[]).with("site_name").and_return("pineapplespring")
expect(subject).to eq(<<~META.chomp)
<meta property="og:title" content="Cunes">
<meta property="og:type" content="profile">
<meta property="og:image" content="http://test.host/images/large/no_avatar.png">
@ -22,55 +24,63 @@ describe ApplicationHelper::GraphMethods, :type => :helper do
<meta property="og:description" content="A bunch of raccoons in a trenchcoat.">
<meta property="og:site_name" content="pineapplespring">
<meta property="profile:username" content="raccoons">
EOS
META
end
end
end
describe "#user_twitter_card" do
context "sample user" do
let(:user) { FactoryBot.create(:user,
profile: {
display_name: '',
description: 'A bunch of raccoons in a trenchcoat.'},
screen_name: 'raccoons') }
let(:user) do
FactoryBot.create(:user,
profile: {
display_name: "",
description: "A bunch of raccoons in a trenchcoat.",
},
screen_name: "raccoons",)
end
subject { user_twitter_card(user) }
it 'should generate a matching OpenGraph structure for a user' do
expect(subject).to eq(<<~EOS.chomp)
it "should generate a matching OpenGraph structure for a user" do
expect(subject).to eq(<<~META.chomp)
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@retrospring">
<meta name="twitter:title" content="Ask me anything!">
<meta name="twitter:description" content="Ask raccoons anything on Retrospring">
<meta name="twitter:image" content="http://test.host/images/large/no_avatar.png">
EOS
META
end
end
end
describe "#answer_opengraph" do
context "sample user and answer" do
let!(:user) { FactoryBot.create(:user,
profile: {
display_name: '',
description: 'A bunch of raccoons in a trenchcoat.'},
screen_name: 'raccoons') }
let(:answer) { FactoryBot.create(:answer,
user_id: user.id,) }
let!(:user) do
FactoryBot.create(:user,
profile: {
display_name: "",
description: "A bunch of raccoons in a trenchcoat.",
},
screen_name: "raccoons",)
end
let(:answer) do
FactoryBot.create(:answer,
user_id: user.id,)
end
subject { answer_opengraph(answer) }
it 'should generate a matching OpenGraph structure for a user' do
allow(APP_CONFIG).to receive(:[]).with('site_name').and_return('pineapplespring')
expect(subject).to eq(<<~EOS.chomp)
it "should generate a matching OpenGraph structure for a user" do
allow(APP_CONFIG).to receive(:[]).with("site_name").and_return("pineapplespring")
expect(subject).to eq(<<~META.chomp)
<meta property="og:title" content="raccoons answered: #{answer.question.content}">
<meta property="og:type" content="article">
<meta property="og:image" content="http://test.host/images/large/no_avatar.png">
<meta property="og:url" content="http://test.host/@raccoons/a/#{answer.id}">
<meta property="og:description" content="#{answer.content}">
<meta property="og:site_name" content="pineapplespring">
EOS
META
end
end
end
end
end

View File

@ -12,8 +12,8 @@ describe ApplicationHelper::TitleMethods, type: :helper do
"anonymous_name" => "Anonymous",
"https" => true,
"items_per_page" => 5,
"sharing" => {}
})
"sharing" => {},
},)
user.profile.display_name = "Cool Man"
user.profile.save!
@ -42,7 +42,7 @@ describe ApplicationHelper::TitleMethods, type: :helper do
context "user has custom anonymous display name" do
before do
FactoryBot.create(:answer, question: question, user: user)
FactoryBot.create(:answer, question:, user:)
user.profile.anon_display_name = "Amogus"
user.profile.save!
end
@ -55,9 +55,9 @@ describe ApplicationHelper::TitleMethods, type: :helper do
describe "#answer_title" do
let(:answer) do
FactoryBot.create(:answer, user: user,
FactoryBot.create(:answer, user:,
content: "a",
question_content: "q")
question_content: "q",)
end
it "should generate a proper title" do

View File

@ -1,45 +1,47 @@
# frozen_string_literal: true
require "rails_helper"
describe BootstrapHelper, :type => :helper do
describe BootstrapHelper, type: :helper do
include ActiveSupport::Testing::TimeHelpers
describe '#nav_entry' do
it 'should return a HTML navigation item which links to a given address' do
describe "#nav_entry" do
it "should return a HTML navigation item which links to a given address" do
allow(self).to receive(:current_page?).and_return(false)
expect(nav_entry('Example', '/example')).to(
eq('<li class="nav-item "><a class="nav-link" href="/example">Example</a></li>')
expect(nav_entry("Example", "/example")).to(
eq('<li class="nav-item "><a class="nav-link" href="/example">Example</a></li>'),
)
end
it 'should return with an active attribute if the link matches the current URL' do
it "should return with an active attribute if the link matches the current URL" do
allow(self).to receive(:current_page?).and_return(true)
expect(nav_entry('Example', '/example')).to(
eq('<li class="nav-item active "><a class="nav-link" href="/example">Example</a></li>')
expect(nav_entry("Example", "/example")).to(
eq('<li class="nav-item active "><a class="nav-link" href="/example">Example</a></li>'),
)
end
it 'should include an icon if given' do
it "should include an icon if given" do
allow(self).to receive(:current_page?).and_return(false)
expect(nav_entry('Example', '/example', icon: 'beaker')).to(
eq('<li class="nav-item "><a class="nav-link" href="/example"><i class="fa fa-beaker"></i> Example</a></li>')
expect(nav_entry("Example", "/example", icon: "beaker")).to(
eq('<li class="nav-item "><a class="nav-link" href="/example"><i class="fa fa-beaker"></i> Example</a></li>'),
)
end
it 'should only include an icon if wanted' do
it "should only include an icon if wanted" do
allow(self).to receive(:current_page?).and_return(false)
expect(nav_entry('Example', '/example', icon: 'beaker', icon_only: true)).to(
eq('<li class="nav-item "><a class="nav-link" href="/example"><i class="fa fa-beaker" title="Example"></i></a></li>')
expect(nav_entry("Example", "/example", icon: "beaker", icon_only: true)).to(
eq('<li class="nav-item "><a class="nav-link" href="/example"><i class="fa fa-beaker" title="Example"></i></a></li>'),
)
end
it 'should include a badge if given' do
it "should include a badge if given" do
allow(self).to receive(:current_page?).and_return(false)
expect(nav_entry('Example', '/example', badge: 3)).to(
eq('<li class="nav-item "><a class="nav-link" href="/example">Example <span class="badge">3</span></a></li>')
expect(nav_entry("Example", "/example", badge: 3)).to(
eq('<li class="nav-item "><a class="nav-link" href="/example">Example <span class="badge">3</span></a></li>'),
)
expect(nav_entry('Example', '/example', badge: 3, badge_color: 'primary', badge_pill: true)).to(
eq('<li class="nav-item "><a class="nav-link" href="/example">Example <span class="badge badge-primary badge-pill">3</span></a></li>')
expect(nav_entry("Example", "/example", badge: 3, badge_color: "primary", badge_pill: true)).to(
eq('<li class="nav-item "><a class="nav-link" href="/example">Example <span class="badge badge-primary badge-pill">3</span></a></li>'),
)
end
@ -52,51 +54,51 @@ describe BootstrapHelper, :type => :helper do
end
describe "#list_group_item" do
it 'should return a HTML navigation item which links to a given address' do
it "should return a HTML navigation item which links to a given address" do
allow(self).to receive(:current_page?).and_return(false)
expect(list_group_item('Example', '/example')).to(
eq('<a href="/example" class="list-group-item list-group-item-action ">Example</a>')
expect(list_group_item("Example", "/example")).to(
eq('<a href="/example" class="list-group-item list-group-item-action ">Example</a>'),
)
end
it 'should return with an active attribute if the link matches the current URL' do
it "should return with an active attribute if the link matches the current URL" do
allow(self).to receive(:current_page?).and_return(true)
expect(list_group_item('Example', '/example')).to(
eq('<a href="/example" class="list-group-item list-group-item-action active ">Example</a>')
expect(list_group_item("Example", "/example")).to(
eq('<a href="/example" class="list-group-item list-group-item-action active ">Example</a>'),
)
end
it 'should include a badge if given' do
it "should include a badge if given" do
allow(self).to receive(:current_page?).and_return(false)
expect(list_group_item('Example', '/example', badge: 3)).to(
eq('<a href="/example" class="list-group-item list-group-item-action ">Example <span class="badge">3</span></a>')
expect(list_group_item("Example", "/example", badge: 3)).to(
eq('<a href="/example" class="list-group-item list-group-item-action ">Example <span class="badge">3</span></a>'),
)
end
end
describe "#bootstrap_color" do
it 'should map error and alert to danger' do
it "should map error and alert to danger" do
expect(bootstrap_color("error")).to eq("danger")
expect(bootstrap_color("alert")).to eq("danger")
end
it 'should map notice to info' do
it "should map notice to info" do
expect(bootstrap_color("notice")).to eq("info")
end
it 'should return any uncovered value' do
it "should return any uncovered value" do
expect(bootstrap_color("success")).to eq("success")
end
end
describe "#tooltip" do
it 'should return the proper markup' do
it "should return the proper markup" do
expect(tooltip("Example Text", "This is in a tooltip")).to eq("<span title=\"This is in a tooltip\" data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\">Example Text</span>")
end
end
describe "#time_tooltip" do
it 'should return a tooltip with proper time values' do
it "should return a tooltip with proper time values" do
travel_to(Time.utc(1984)) do
@user = FactoryBot.create(:user)
travel 10.minutes
@ -107,7 +109,7 @@ describe BootstrapHelper, :type => :helper do
end
describe "#hidespan" do
it 'should return the proper markup' do
it "should return the proper markup" do
expect(hidespan("Hidden Text", "d-none")).to eq("<span class=\"d-none\">Hidden Text</span>")
end
end

View File

@ -11,9 +11,9 @@ describe FeedbackHelper, type: :helper do
"canny" => {
sso: "sso",
feature_board: "feature",
bug_board: "bug"
}
})
bug_board: "bug",
},
},)
end
describe "#canny_token" do

View File

@ -10,8 +10,8 @@ describe MarkdownHelper, type: :helper do
"items_per_page" => 5,
"allowed_hosts" => [
"twitter.com"
]
})
],
},)
end
describe "#markdown" do

View File

@ -9,7 +9,7 @@ describe SocialHelper::TelegramMethods, type: :helper do
:answer,
user:,
content: "this is an answer\nwith multiple lines\nand **FORMATTING**",
question_content: "this is a question .... or is it?"
question_content: "this is a question .... or is it?",
)
end
@ -18,7 +18,7 @@ describe SocialHelper::TelegramMethods, type: :helper do
"hostname" => "example.com",
"https" => true,
"items_per_page" => 5,
})
},)
end
describe "#telegram_text" do

View File

@ -1,33 +1,35 @@
# frozen_string_literal: true
require 'rails_helper'
require "rails_helper"
describe SocialHelper::TumblrMethods, :type => :helper do
describe SocialHelper::TumblrMethods, type: :helper do
let(:user) { FactoryBot.create(:user) }
let(:answer) { FactoryBot.create(:answer, user: user,
content: 'aaaa',
question_content: 'q') }
let(:answer) do
FactoryBot.create(:answer, user:,
content: "aaaa",
question_content: "q",)
end
before do
stub_const("APP_CONFIG", {
'hostname' => 'example.com',
'anonymous_name' => 'Anonymous',
'https' => true,
'items_per_page' => 5,
'sharing' => {}
})
"hostname" => "example.com",
"anonymous_name" => "Anonymous",
"https" => true,
"items_per_page" => 5,
"sharing" => {},
},)
end
describe '#tumblr_title' do
context 'Asker is anonymous' do
describe "#tumblr_title" do
context "Asker is anonymous" do
subject { tumblr_title(answer) }
it 'should return a proper title' do
expect(subject).to eq('Anonymous asked: q')
it "should return a proper title" do
expect(subject).to eq("Anonymous asked: q")
end
end
context 'Asker is known' do
context "Asker is known" do
before do
@user = FactoryBot.create(:user)
answer.question.user = @user
@ -36,24 +38,24 @@ describe SocialHelper::TumblrMethods, :type => :helper do
subject { tumblr_title(answer) }
it 'should return a proper title' do
it "should return a proper title" do
expect(subject).to eq("#{answer.question.user.profile.display_name} asked: q")
end
end
end
describe '#tumblr_body' do
describe "#tumblr_body" do
subject { tumblr_body(answer) }
it 'should return a proper body' do
it "should return a proper body" do
expect(subject).to eq("aaaa\n\n[Smile or comment on the answer here](https://example.com/@#{answer.user.screen_name}/a/#{answer.id})")
end
end
describe '#tumblr_share_url' do
describe "#tumblr_share_url" do
subject { tumblr_share_url(answer) }
it 'should return a proper share link' do
it "should return a proper share link" do
expect(subject).to eq("https://www.tumblr.com/widgets/share/tool?shareSource=legacy&posttype=text&title=#{CGI.escape(tumblr_title(answer))}&url=#{CGI.escape("https://example.com/@#{answer.user.screen_name}/a/#{answer.id}")}&caption=&content=#{CGI.escape(tumblr_body(answer))}")
end
end

View File

@ -1,39 +1,41 @@
# frozen_string_literal: true
require 'rails_helper'
require "rails_helper"
describe SocialHelper::TwitterMethods, :type => :helper do
describe SocialHelper::TwitterMethods, type: :helper do
let(:user) { FactoryBot.create(:user) }
let(:question_content) { 'q' * 255 }
let(:answer_content) { 'a' * 255 }
let(:answer) { FactoryBot.create(:answer, user: user,
content: answer_content,
question_content: question_content) }
let(:question_content) { "q" * 255 }
let(:answer_content) { "a" * 255 }
let(:answer) do
FactoryBot.create(:answer, user:,
content: answer_content,
question_content:,)
end
before do
stub_const("APP_CONFIG", {
'hostname' => 'example.com',
'https' => true,
'items_per_page' => 5
})
"hostname" => "example.com",
"https" => true,
"items_per_page" => 5,
},)
end
describe '#prepare_tweet' do
context 'when the question and answer need to be shortened' do
describe "#prepare_tweet" do
context "when the question and answer need to be shortened" do
subject { prepare_tweet(answer) }
it 'should return a properly formatted tweet' do
it "should return a properly formatted tweet" do
expect(subject).to eq("#{'q' * 123}… — #{'a' * 124}… https://example.com/@#{user.screen_name}/a/#{answer.id}")
end
end
context 'when a suffix has been passed' do
let(:question_content) { 'question' }
let(:answer_content) { 'answer' }
context "when a suffix has been passed" do
let(:question_content) { "question" }
let(:answer_content) { "answer" }
subject { prepare_tweet(answer, '#askracc') }
subject { prepare_tweet(answer, "#askracc") }
it 'should include the suffix after the link' do
it "should include the suffix after the link" do
expect(subject).to eq("question — answer #askracc https://example.com/@#{user.screen_name}/a/#{answer.id}")
end
end
@ -49,34 +51,34 @@ describe SocialHelper::TwitterMethods, :type => :helper do
end
end
context 'when a suffix has been passed and the tweet needs to be shortened' do
subject { prepare_tweet(answer, '#askracc') }
context "when a suffix has been passed and the tweet needs to be shortened" do
subject { prepare_tweet(answer, "#askracc") }
it 'should shorten the tweet while keeping the suffix intact' do
it "should shorten the tweet while keeping the suffix intact" do
expect(subject).to eq("#{'q' * 120}… — #{'a' * 120}#askracc https://example.com/@#{user.screen_name}/a/#{answer.id}")
end
end
context 'when the question and answer are short' do
context "when the question and answer are short" do
before do
answer.question.content = 'Why are raccoons so good?'
answer.question.content = "Why are raccoons so good?"
answer.question.save!
answer.content = 'Because they are good cunes.'
answer.content = "Because they are good cunes."
answer.save!
end
subject { prepare_tweet(answer) }
it 'should return a properly formatted tweet' do
it "should return a properly formatted tweet" do
expect(subject).to eq("#{answer.question.content}#{answer.content} https://example.com/@#{user.screen_name}/a/#{answer.id}")
end
end
end
describe '#twitter_share_url' do
describe "#twitter_share_url" do
subject { twitter_share_url(answer) }
it 'should return a proper share link' do
it "should return a proper share link" do
expect(subject).to eq("https://twitter.com/intent/tweet?text=#{CGI.escape(prepare_tweet(answer))}")
end
end

View File

@ -18,8 +18,7 @@ describe SocialHelper, type: :helper do
"hostname" => "example.com",
"https" => true,
"items_per_page" => 5,
},
)
},)
end
describe "#answer_share_url" do

View File

@ -2,7 +2,7 @@
require "rails_helper"
describe ThemeHelper, :type => :helper do
describe ThemeHelper, type: :helper do
describe "#render_theme" do
context "when target page doesn't have a theme" do
it "returns no theme" do
@ -18,7 +18,7 @@ describe ThemeHelper, :type => :helper do
end
it "returns a theme" do
expect(helper.render_theme).to include('<style>:root {')
expect(helper.render_theme).to include("<style>:root {")
end
it "contains correct theme background colors" do
@ -193,12 +193,12 @@ describe ThemeHelper, :type => :helper do
end
end
describe '#theme_color' do
describe "#theme_color" do
subject { helper.theme_color }
context 'when user is signed in' do
context "when user is signed in" do
let(:user) { FactoryBot.create(:user) }
let(:theme) { FactoryBot.create(:theme, user: user) }
let(:theme) { FactoryBot.create(:theme, user:) }
before(:each) do
user.theme = theme
@ -206,24 +206,24 @@ describe ThemeHelper, :type => :helper do
sign_in(user)
end
it 'should return the user theme\'s primary color' do
expect(subject).to eq('#8e8cd8')
it "should return the user theme's primary color" do
expect(subject).to eq("#8e8cd8")
end
end
context 'user is not signed in' do
it 'should return the default primary color' do
expect(subject).to eq('#5e35b1')
context "user is not signed in" do
it "should return the default primary color" do
expect(subject).to eq("#5e35b1")
end
end
end
describe '#mobile_theme_color' do
describe "#mobile_theme_color" do
subject { helper.mobile_theme_color }
context 'when user is signed in' do
context "when user is signed in" do
let(:user) { FactoryBot.create(:user) }
let(:theme) { FactoryBot.create(:theme, user: user) }
let(:theme) { FactoryBot.create(:theme, user:) }
before(:each) do
user.theme = theme
@ -231,14 +231,14 @@ describe ThemeHelper, :type => :helper do
sign_in(user)
end
it 'should return the user theme\'s background color' do
expect(subject).to eq('#c6c5eb')
it "should return the user theme's background color" do
expect(subject).to eq("#c6c5eb")
end
end
context 'user is not signed in' do
it 'should return the default background color' do
expect(subject).to eq('#f0edf4')
context "user is not signed in" do
it "should return the default background color" do
expect(subject).to eq("#f0edf4")
end
end
end

View File

@ -6,10 +6,10 @@ describe UserHelper, type: :helper do
describe "#user_screen_name" do
subject do
helper.user_screen_name(user,
context_user: context_user,
author_identifier: author_identifier,
url: url,
link_only: link_only)
context_user:,
author_identifier:,
url:,
link_only:,)
end
let(:user) { FactoryBot.create(:user) }
@ -20,8 +20,8 @@ describe UserHelper, type: :helper do
before do
stub_const("APP_CONFIG", {
"anonymous_name" => "Anonymous"
})
"anonymous_name" => "Anonymous",
},)
end
context "moderation view enabled" do
@ -80,7 +80,7 @@ describe UserHelper, type: :helper do
context "author is anonymous" do
let(:author_identifier) { "some_identifier" }
let(:context_user) { FactoryBot.create(:user, profile: { anon_display_name: anon_display_name }) }
let(:context_user) { FactoryBot.create(:user, profile: { anon_display_name: }) }
context "context user has custom anonymous name" do
let(:anon_display_name) { "Sneaky Raccoon" }

26
spec/models/inbox_spec.rb Normal file
View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require "rails_helper"
describe Inbox, type: :model do
describe "associations" do
it { should belong_to(:user) }
it { should belong_to(:question) }
end
describe "before_destroy" do
let(:user) { FactoryBot.create(:user) }
let(:question) { FactoryBot.create(:question, author_is_anonymous: true) }
it "does not fail if the user wants to delete their account" do
Inbox.create(user:, question:)
# this deletes the User record and enqueues the deletion of all
# associated records in sidekiq
user.destroy!
# so let's drain the queues
expect { Sidekiq::Worker.drain_all }.not_to raise_error
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require "rails_helper"
describe Notification, type: :model do
describe "associations" do
it { should belong_to(:recipient) }
it { should belong_to(:target) }
end
describe "before_destroy" do
let(:user) { FactoryBot.create(:user) }
let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) }
it "does not fail if the user wants to delete their account" do
Notification::QuestionAnswered.create(recipient: user, target: answer)
# this deletes the User record and enqueues the deletion of all
# associated records in sidekiq
user.destroy!
# so let's drain the queues
expect { Sidekiq::Worker.drain_all }.not_to raise_error
end
end
end