Commit 659dfa75 authored by Nicolas Dular's avatar Nicolas Dular

New group and project invite email design

This makes the invite mail experiment the default experience and
therefore improves our invitation emails by having one clear call to
action with a neater design.
parent ca29a313
......@@ -119,7 +119,6 @@ linters:
- 'app/views/invites/show.html.haml'
- 'app/views/jira_connect/subscriptions/index.html.haml'
- 'app/views/layouts/_mailer.html.haml'
- 'app/views/layouts/experiment_mailer.html.haml'
- 'app/views/layouts/header/_default.html.haml'
- 'app/views/layouts/header/_new_dropdown.haml'
- 'app/views/layouts/jira_connect.html.haml'
......
......@@ -143,4 +143,21 @@ tr.footer td {
color: $mailer-link-color;
text-decoration: none;
}
.gitlab-info {
padding: $gl-padding-24 0;
}
.gitlab-info-text {
max-width: 640px;
margin: 0 auto;
text-align: center;
color: $gray-400;
font-size: $gl-font-size-small;
}
.footer-logo {
width: 90px;
height: 33px;
}
}
......@@ -15,13 +15,11 @@ class InvitesController < ApplicationController
feature_category :authentication_and_authorization
def show
track_new_user_invite_experiment('opened')
accept if skip_invitation_prompt?
end
def accept
if member.accept_invite!(current_user)
track_new_user_invite_experiment('accepted')
track_invitation_reminders_experiment('accepted')
redirect_to invite_details[:path], notice: _("You have been granted %{member_human_access} access to %{title} %{name}.") %
{ member_human_access: member.human_access, title: invite_details[:title], name: invite_details[:name] }
......@@ -110,25 +108,13 @@ class InvitesController < ApplicationController
end
end
def track_new_user_invite_experiment(action)
return unless params[:new_user_invite]
property = params[:new_user_invite] == 'experiment' ? 'experiment_group' : 'control_group'
track_experiment(:invite_email, action, property)
end
def track_invitation_reminders_experiment(action)
return unless Gitlab::Experimentation.enabled?(:invitation_reminders)
property = Gitlab::Experimentation.enabled_for_attribute?(:invitation_reminders, member.invite_email) ? 'experimental_group' : 'control_group'
track_experiment(:invitation_reminders, action, property)
end
def track_experiment(experiment_key, action, property)
Gitlab::Tracking.event(
Gitlab::Experimentation.experiment(experiment_key).tracking_category,
Gitlab::Experimentation.experiment(:invitation_reminders).tracking_category,
action,
property: property,
label: Digest::MD5.hexdigest(member.to_global_id.to_s)
......
......@@ -51,34 +51,20 @@ module Emails
return unless member_exists?
subject_line = subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}")
if member.invite_to_unknown_user? && Feature.enabled?(:invite_email_experiment)
subject_line = subject("#{member.created_by.name} invited you to join GitLab") if member.created_by
@invite_url_params = { new_user_invite: 'experiment' }
member_email_with_layout(
to: member.invite_email,
subject: subject_line,
template: 'member_invited_email_experiment',
layout: 'experiment_mailer'
)
Gitlab::Tracking.event(Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category], 'sent', property: 'experiment_group')
else
@invite_url_params = member.invite_to_unknown_user? ? { new_user_invite: 'control' } : {}
member_email_with_layout(
to: member.invite_email,
subject: subject_line
)
if member.invite_to_unknown_user?
Gitlab::Tracking.event(Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category], 'sent', property: 'control_group')
subject_line =
if member.created_by
subject(s_("MemberInviteEmail|%{member_name} invited you to join GitLab") % { member_name: member.created_by.name })
else
subject(s_("MemberInviteEmail|Invitation to join the %{project_or_group} %{project_or_group_name}") % { project_or_group: member_source.human_name, project_or_group_name: member_source.model_name.singular })
end
end
if member.invite_to_unknown_user? && Gitlab::Experimentation.enabled?(:invitation_reminders)
member_email_with_layout(
to: member.invite_email,
subject: subject_line,
layout: 'unknown_user_mailer'
)
if Gitlab::Experimentation.enabled?(:invitation_reminders)
Gitlab::Tracking.event(
Gitlab::Experimentation.experiment(:invitation_reminders).tracking_category,
'sent',
......@@ -105,7 +91,7 @@ module Emails
subject_line = subjects[reminder_index] % { inviter: member.created_by.name }
member_email_with_layout(
layout: 'experiment_mailer',
layout: 'unknown_user_mailer',
to: member.invite_email,
subject: subject(subject_line)
)
......@@ -162,15 +148,10 @@ module Emails
@member_source_type.classify.constantize
end
def member_email_with_layout(to:, subject:, template: nil, layout: 'mailer')
def member_email_with_layout(to:, subject:, layout: 'mailer')
mail(to: to, subject: subject) do |format|
if template
format.html { render template, layout: layout }
format.text { render template, layout: layout }
else
format.html { render layout: layout }
format.text { render layout: layout }
end
format.html { render layout: layout }
format.text { render layout: layout }
end
end
end
......
......@@ -25,5 +25,5 @@
- if !member?
.actions
= link_to _("Accept invitation"), accept_invite_url(@token, new_user_invite: params[:new_user_invite]), method: :post, class: "btn gl-button btn-success"
= link_to _("Accept invitation"), accept_invite_url(@token), method: :post, class: "btn gl-button btn-success"
= link_to _("Decline"), decline_invite_url(@token), method: :post, class: "btn gl-button btn-danger gl-ml-3"
......@@ -34,13 +34,7 @@
= render_if_exists 'layouts/mailer/additional_text'
%tr.footer
%td
%img{ alt: "GitLab", height: "33", width: "90", src: image_url('mailers/gitlab_footer_logo.gif') }
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, class: 'mng-notif-link')
- help_link = link_to(_("Help"), help_url, class: 'help-link')
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= yield :footer
= yield :additional_footer
%tr
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
%title= message.subject
-# Avoid premailer processing of client-specific styles (@media tag not supported)
-# We need to inline the contents here because mail clients (e.g. iOS Mail, Outlook)
-# do not support linked stylesheets.
%style{ type: 'text/css', 'data-premailer': 'ignore' }
= asset_to_string('mailer_client_specific.css').html_safe
= stylesheet_link_tag 'mailer.css'
%body
%table#body{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
%tr.line
%td
%tr.header
%td
= html_header_message
= header_logo
%tr
%td
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
%tr
%td.wrapper-cell{ style: "padding: 0" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0" }
%tbody
= yield
= render_if_exists 'layouts/mailer/additional_text'
%tr.footer
%td{ style: "padding: 24px 0" }
%img{ alt: "GitLab", height: "33", width: "90", src: image_url('mailers/gitlab_footer_logo.gif') }
%p{ style: "color: #949ba5; max-width: 640px; margin: 0 auto; text-align: left; font-size: 12px;" }
GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way
%br
Development, Security, and Ops teams collaborate.
= yield :additional_footer
%tr
%td.footer-message
= html_footer_message
= content_for :footer do
%tr.footer
%td
%img.footer-logo{ alt: "GitLab", src: image_url('mailers/gitlab_footer_logo.gif') }
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, class: 'mng-notif-link')
- help_link = link_to(_("Help"), help_url, class: 'help-link')
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
= render 'layouts/mailer'
= content_for :footer do
%tr.footer
%td.gitlab-info
%img.footer-logo{ alt: "GitLab", src: image_url('mailers/gitlab_footer_logo.gif') }
%p.gitlab-info-text
= html_escape(_("GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way%{br_tag}Development, Security, and Ops teams collaborate")) % { br_tag: '<br/>'.html_safe }
= render 'layouts/mailer'
<%= text_header_message %>
<%= yield -%>
-- <%# signature marker %>
<%= _("GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate") %>
<%= render_if_exists 'layouts/mailer/additional_text' %>
<%= text_footer_message %>
- placeholders = { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_or_group_name: member_source.human_name, project_or_group: member_source.model_name.singular, br_tag: '<br/>'.html_safe, role: member.human_access.downcase }
%tr
%td.text-content
%h2.invite-header
= s_('InviteEmail|You are invited!')
%p
You have been invited
- if member.created_by
by
= link_to member.created_by.name, user_url(member.created_by)
to join the
= link_to member_source.human_name, member_source.public? ? member_source.web_url : invite_url(@token), class: :highlight
#{member_source.model_name.singular} as #{content_tag :span, member.human_access, class: :highlight}.
%p
= link_to 'Accept invitation', invite_url(@token, @invite_url_params)
or
= link_to 'decline', decline_invite_url(@token)
= html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders.merge({ inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe })
- else
= html_escape(s_("InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders
%p.invite-actions
= link_to s_('InviteEmail|Join now'), invite_url(@token), class: 'invite-btn-join'
You have been invited <%= "by #{sanitize_name(member.created_by.name)} " if member.created_by %>to join the <%= member_source.human_name %> <%= member_source.model_name.singular %> as <%= member.human_access %>.
<% placeholders = { project_or_group_name: member_source.human_name, project_or_group: member_source.model_name.singular, role: member.human_access.downcase } %>
Accept invitation: <%= invite_url(@token, @invite_url_params) %>
Decline invitation: <%= decline_invite_url(@token) %>
<% if member.created_by %>
<%= s_('InviteEmail|%{inviter} invited you to join the %{project_or_group_name} %{project_or_group} as a %{role}') % placeholders.merge({ inviter: sanitize_name(member.created_by.name) }) %>
<% else %>
<%= s_('InviteEmail|You have been invited to join the %{project_or_group_name} %{project_or_group} as a %{role}') % placeholders %>
<% end %>
<%= s_('InviteEmail|Join now') %>: <%= invite_url(@token) %>
%tr
%td.text-content
%h2.invite-header
= s_('InviteEmail|You are invited!')
%p
- if member.created_by
= html_escape(s_("InviteEmail|%{inviter} invited you")) % { inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe }
= html_escape(s_("InviteEmail|to join the %{strong_start}%{project_or_group_name}%{strong_end}")) % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_or_group_name: member_source.human_name }
%br
= s_("InviteEmail|%{project_or_group} as a %{role}") % { project_or_group: member_source.model_name.singular, role: member.human_access.downcase }
%p.invite-actions
= link_to s_('InviteEmail|Join now'), invite_url(@token, @invite_url_params), class: 'invite-btn-join'
<% project_and_role = s_('InviteEmail|to join the %{project_or_group_name} %{project_or_group} as a %{role}') \
% { project_or_group_name: member_source.human_name, project_or_group: member_source.model_name.singular, role: member.human_access.downcase } %>
<% if member.created_by %>
<%= s_('InviteEmail|%{inviter} invited you') % { inviter: sanitize_name(member.created_by.name) } %> <%= project_and_role %>
<% else %>
<%= s_('InviteEmail|You have been invited') %> <%= project_and_role %>
<% end %>
Join now: <%= invite_url(@token, @invite_url_params) %>
---
title: New group and project invite mail design
merge_request: 44940
author:
type: changed
......@@ -12205,6 +12205,12 @@ msgstr ""
msgid "GitLab for Slack"
msgstr ""
msgid "GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate"
msgstr ""
msgid "GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way%{br_tag}Development, Security, and Ops teams collaborate"
msgstr ""
msgid "GitLab is a single application for the entire software development lifecycle. From project planning and source code management to CI/CD, monitoring, and security."
msgstr ""
......@@ -14249,25 +14255,22 @@ msgstr ""
msgid "Invite teammates (optional)"
msgstr ""
msgid "InviteEmail|%{inviter} invited you"
msgid "InviteEmail|%{inviter} invited you to join the %{project_or_group_name} %{project_or_group} as a %{role}"
msgstr ""
msgid "InviteEmail|%{project_or_group} as a %{role}"
msgid "InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}"
msgstr ""
msgid "InviteEmail|Join now"
msgstr ""
msgid "InviteEmail|You are invited!"
msgstr ""
msgid "InviteEmail|You have been invited"
msgid "InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}"
msgstr ""
msgid "InviteEmail|to join the %{project_or_group_name} %{project_or_group} as a %{role}"
msgid "InviteEmail|You are invited!"
msgstr ""
msgid "InviteEmail|to join the %{strong_start}%{project_or_group_name}%{strong_end}"
msgid "InviteEmail|You have been invited to join the %{project_or_group_name} %{project_or_group} as a %{role}"
msgstr ""
msgid "InviteMembersBanner|Collaborate with your team"
......@@ -16065,6 +16068,12 @@ msgstr ""
msgid "Member since %{date}"
msgstr ""
msgid "MemberInviteEmail|%{member_name} invited you to join GitLab"
msgstr ""
msgid "MemberInviteEmail|Invitation to join the %{project_or_group} %{project_or_group_name}"
msgstr ""
msgid "Members"
msgstr ""
......
......@@ -9,13 +9,6 @@ RSpec.describe InvitesController, :snowplow do
let(:project_members) { member.source.users }
let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) }
let(:params) { { id: raw_invite_token } }
let(:snowplow_event) do
{
category: 'Growth::Acquisition::Experiment::InviteEmail',
label: md5_member_global_id,
property: group_type
}
end
shared_examples 'invalid token' do
context 'when invite token is not valid' do
......@@ -94,38 +87,6 @@ RSpec.describe InvitesController, :snowplow do
expect(flash[:notice]).to be_nil
end
context 'when new_user_invite is not set' do
it 'does not track the user as experiment group' do
request
expect_no_snowplow_event
end
end
context 'when new_user_invite is experiment' do
let(:params) { { id: raw_invite_token, new_user_invite: 'experiment' } }
let(:group_type) { 'experiment_group' }
it 'tracks the user as experiment group' do
request
expect_snowplow_event(**snowplow_event.merge(action: 'opened'))
expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
context 'when new_user_invite is control' do
let(:params) { { id: raw_invite_token, new_user_invite: 'control' } }
let(:group_type) { 'control_group' }
it 'tracks the user as control group' do
request
expect_snowplow_event(**snowplow_event.merge(action: 'opened'))
expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
it_behaves_like "tracks the 'accepted' event for the invitation reminders experiment"
it_behaves_like 'invalid token'
end
......@@ -158,36 +119,6 @@ RSpec.describe InvitesController, :snowplow do
subject(:request) { post :accept, params: params }
context 'when new_user_invite is not set' do
it 'does not track an event' do
request
expect_no_snowplow_event
end
end
context 'when new_user_invite is experiment' do
let(:params) { { id: raw_invite_token, new_user_invite: 'experiment' } }
let(:group_type) { 'experiment_group' }
it 'tracks the user as experiment group' do
request
expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
context 'when new_user_invite is control' do
let(:params) { { id: raw_invite_token, new_user_invite: 'control' } }
let(:group_type) { 'control_group' }
it 'tracks the user as control group' do
request
expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
it_behaves_like "tracks the 'accepted' event for the invitation reminders experiment"
it_behaves_like 'invalid token'
end
......
......@@ -887,96 +887,30 @@ RSpec.describe Notify do
subject { described_class.member_invited_email('project', project_member.id, project_member.invite_token) }
context 'when invite_email_experiment is disabled' do
before do
stub_feature_flags(invite_email_experiment: false)
end
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
it_behaves_like 'does not render a manage notifications link'
context 'when there is an inviter' do
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.full_name} project"
is_expected.to have_subject "#{inviter.name} invited you to join GitLab"
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
end
context 'when member is invited via an email address' do
it 'does add a param to the invite link' do
is_expected.to have_body_text 'new_user_invite=control'
end
it 'tracks an event' do
expect(Gitlab::Tracking).to receive(:event).with(
'Growth::Acquisition::Experiment::InviteEmail',
'sent',
property: 'control_group'
)
subject.deliver_now
end
end
context 'when member is already a user' do
let(:project_member) { invite_to_project(project, inviter: maintainer, user: create(:user)) }
it 'does not add a param to the invite link' do
is_expected.not_to have_body_text 'new_user_invite'
end
it 'does not track an event' do
expect(Gitlab::Tracking).not_to receive(:event)
subject.deliver_now
end
end
end
context 'when invite_email_experiment is enabled' do
before do
stub_feature_flags(invite_email_experiment: true)
end
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
context 'when there is no inviter' do
let(:inviter) { nil }
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.full_name} project"
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
end
end
context 'when there is no inviter' do
let(:inviter) { nil }
context 'when there is an inviter' do
it 'contains all the useful information' do
is_expected.to have_subject "#{inviter.name} invited you to join GitLab"
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
end
end
it 'adds a param to the invite link' do
is_expected.to have_body_text 'new_user_invite=experiment'
end
it 'tracks an event' do
expect(Gitlab::Tracking).to receive(:event).with(
'Growth::Acquisition::Experiment::InviteEmail',
'sent',
property: 'experiment_group'
)
subject.deliver_now
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{project.full_name} project"
is_expected.to have_body_text project.full_name
is_expected.to have_body_text project_member.human_access.downcase
is_expected.to have_body_text project_member.invite_token
end
end
end
......@@ -1547,95 +1481,31 @@ RSpec.describe Notify do
end
end
context 'when invite_email_experiment is disabled' do
before do
stub_feature_flags(invite_email_experiment: false)
end
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
it_behaves_like 'it requires a group'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
it_behaves_like 'it requires a group'
it_behaves_like 'does not render a manage notifications link'
context 'when there is an inviter' do
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
is_expected.to have_subject "#{group_member.created_by.name} invited you to join GitLab"
is_expected.to have_body_text group.name
is_expected.to have_body_text group.web_url
is_expected.to have_body_text group_member.human_access
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text group_member.invite_token
end
context 'when member is invited via an email address' do
it 'does add a param to the invite link' do
is_expected.to have_body_text 'new_user_invite=control'
end
it 'tracks an event' do
expect(Gitlab::Tracking).to receive(:event).with(
'Growth::Acquisition::Experiment::InviteEmail',
'sent',
property: 'control_group'
)
subject.deliver_now
end
end
context 'when member is already a user' do
let(:group_member) { invite_to_group(group, inviter: owner, user: create(:user)) }
it 'does not add a param to the invite link' do
is_expected.not_to have_body_text 'new_user_invite'
end
it 'does not track an event' do
expect(Gitlab::Tracking).not_to receive(:event)
subject.deliver_now
end
end
end
context 'when invite_email_experiment is enabled' do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'it requires a group'
context 'when there is no inviter' do
let(:inviter) { nil }
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
is_expected.to have_body_text group.name
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text group_member.invite_token
end
end
context 'when there is no inviter' do
let(:inviter) { nil }
context 'when there is an inviter' do
it 'contains all the useful information' do
is_expected.to have_subject "#{group_member.created_by.name} invited you to join GitLab"
is_expected.to have_body_text group.name
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text group_member.invite_token
end
end
it 'does add a param to the invite link' do
is_expected.to have_body_text 'new_user_invite'
end
it 'tracks an event' do
expect(Gitlab::Tracking).to receive(:event).with(
'Growth::Acquisition::Experiment::InviteEmail',
'sent',
property: 'experiment_group'
)
subject.deliver_now
it 'contains all the useful information' do
is_expected.to have_subject "Invitation to join the #{group.name} group"
is_expected.to have_body_text group.name
is_expected.to have_body_text group_member.human_access.downcase
is_expected.to have_body_text group_member.invite_token
end
end
end
......
......@@ -273,3 +273,12 @@ RSpec.shared_examples 'no email is sent' do
expect(subject.message).to be_a_kind_of(ActionMailer::Base::NullMail)
end
end
RSpec.shared_examples 'does not render a manage notifications link' do
it do
aggregate_failures do
expect(subject).not_to have_body_text("Manage all notifications")
expect(subject).not_to have_body_text(profile_notifications_url)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment