diff --git a/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs b/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs index 9332b72c82b..25cb311913a 100644 --- a/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs +++ b/app/assets/javascripts/discourse/app/templates/user-invited-show.hbs @@ -184,24 +184,26 @@ {{/if}} - - - - + {{#if invite.can_delete_invite}} + + + + + {{/if}} {{/each}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js index 0858762c635..e2ad32b9f56 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-invite-modal-test.js @@ -1,7 +1,6 @@ import { click, fillIn, visit } from "@ember/test-helpers"; import { acceptance, - count, exists, fakeTime, loggedInUser, @@ -57,15 +56,15 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) { await visit("/u/eviltrout/invited/pending"); await click(".user-invite-buttons .btn:first-child"); - assert.ok(!exists("tbody tr"), "does not show invite before saving"); + assert + .dom("table.user-invite-list tbody tr") + .exists({ count: 2 }, "is seeded with two rows"); await click(".btn-primary"); - assert.strictEqual( - count("tbody tr"), - 1, - "adds invite to list after saving" - ); + assert + .dom("table.user-invite-list tbody tr") + .exists({ count: 3 }, "gets added to the list"); }); test("copying saves invite", async function (assert) { diff --git a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js index a443bd9596d..3943e5b9fab 100644 --- a/app/assets/javascripts/discourse/tests/helpers/create-pretender.js +++ b/app/assets/javascripts/discourse/tests/helpers/create-pretender.js @@ -224,7 +224,40 @@ export function applyDefaultHandlers(pretender) { pretender.get("/u/eviltrout/invited.json", () => { return response({ - invites: [], + invites: [ + { + id: 8, + invite_key: "hMFT8G1oKP", + link: "http://localhost:3000/invites/hMFT8G1oKP", + email: "steak@cat.com", + domain: null, + emailed: false, + can_delete_invite: true, + custom_message: null, + created_at: "2023-06-01T04:47:13.195Z", + updated_at: "2023-06-01T04:47:13.195Z", + expires_at: "2023-08-30T04:47:00.000Z", + expired: false, + topics: [], + groups: [], + }, + { + id: 9, + invite_key: "hMFT8G1WHA", + link: "http://localhost:3000/invites/hMFT8G1WHA", + email: "tomtom@cat.com", + domain: null, + emailed: false, + can_delete_invite: false, + custom_message: null, + created_at: "2023-06-01T04:47:13.195Z", + updated_at: "2023-06-01T04:47:13.195Z", + expires_at: "2023-08-30T04:47:00.000Z", + expired: false, + topics: [], + groups: [], + }, + ], can_see_invite_details: true, counts: { pending: 0, diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-invited-show-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-invited-show-test.js new file mode 100644 index 00000000000..686a340ad64 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/user-invited-show-test.js @@ -0,0 +1,19 @@ +import { visit } from "@ember/test-helpers"; +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import { test } from "qunit"; + +acceptance("User invites", function (needs) { + needs.user(); + + test("hides delete button based on can_delete_invite", async function (assert) { + await visit("/u/eviltrout/invited"); + + assert.dom("table.user-invite-list tbody tr").exists({ count: 2 }); + assert + .dom("table.user-invite-list tbody tr:nth-child(1) button.cancel") + .exists(); + assert + .dom("table.user-invite-list tbody tr:nth-child(2) button.cancel") + .doesNotExist(); + }); +}); diff --git a/app/serializers/invite_serializer.rb b/app/serializers/invite_serializer.rb index 5ca6021c9a1..9e49bdfa640 100644 --- a/app/serializers/invite_serializer.rb +++ b/app/serializers/invite_serializer.rb @@ -7,6 +7,7 @@ class InviteSerializer < ApplicationSerializer :email, :domain, :emailed, + :can_delete_invite, :max_redemptions_allowed, :redemption_count, :custom_message, @@ -30,6 +31,10 @@ class InviteSerializer < ApplicationSerializer object.emailed_status != Invite.emailed_status_types[:not_required] end + def can_delete_invite + scope.is_admin? || object.invited_by_id == scope.current_user.id + end + def include_custom_message? email.present? end diff --git a/spec/serializers/invite_serializer_spec.rb b/spec/serializers/invite_serializer_spec.rb new file mode 100644 index 00000000000..c0a721b733f --- /dev/null +++ b/spec/serializers/invite_serializer_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.describe InviteSerializer do + describe "#can_delete_invite" do + fab!(:user) { Fabricate(:user) } + fab!(:admin) { Fabricate(:admin) } + fab!(:moderator) { Fabricate(:moderator) } + fab!(:invite_from_user) { Fabricate(:invite, invited_by: user) } + fab!(:invite_from_moderator) { Fabricate(:invite, invited_by: moderator) } + + it "returns true for admin" do + serializer = InviteSerializer.new(invite_from_user, scope: Guardian.new(admin), root: false) + + expect(serializer.as_json[:can_delete_invite]).to eq(true) + end + + it "returns false for moderator" do + serializer = + InviteSerializer.new(invite_from_user, scope: Guardian.new(moderator), root: false) + + expect(serializer.as_json[:can_delete_invite]).to eq(false) + end + + it "returns true for inviter" do + serializer = InviteSerializer.new(invite_from_user, scope: Guardian.new(user), root: false) + + expect(serializer.as_json[:can_delete_invite]).to eq(true) + end + + it "returns false for plain user" do + serializer = + InviteSerializer.new(invite_from_moderator, scope: Guardian.new(user), root: false) + + expect(serializer.as_json[:can_delete_invite]).to eq(false) + end + end +end