Browse Source

Implement tag deduplication, allowing multiple authors to tag again.

develop
Bèr Kessels 10 months ago
parent
commit
c30e47c702

+ 3
- 2
app/aggregates/member.rb View File

@@ -2,6 +2,7 @@

require_relative 'mixins/attributes'
require_relative 'member/tag'
require_relative 'member/tag_list'

require 'lib/aggregate_equality'

@@ -16,7 +17,7 @@ module Aggregates
include Attributes

def initialize(id, events)
@tags = []
@tags = TagList.new
super(id, events)
end

@@ -44,7 +45,7 @@ module Aggregates
end

apply MemberTagAdded do |event|
@tags << Tag.new(event.body['tag'])
@tags << Tag.new(event.body['tag'], event.body['author_id'])
end

def add_member(payload)

+ 19
- 3
app/aggregates/member/tag.rb View File

@@ -8,16 +8,32 @@ module Aggregates
class Tag
attr_reader :name

def initialize(name)
def initialize(name, author)
@name = name
@authors = Set[author]
end

def slug
name.downcase
end

def by?(_expected_author)
true
def authors
@authors.to_a
end

def by?(expected_author)
@authors.include?(expected_author)
end

def ==(other)
return if other.nil?

name == other.name
end

def merge(other)
@authors += other.authors
self
end
end
end

+ 23
- 0
app/aggregates/member/tag_list.rb View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Aggregates
class Member
##
# A +TagList+ is an ordered Set. Ensuring tags with same names are merged.
class TagList < Array
def <<(other)
if include?(other)
find_original(other).merge(other)
else
super(other)
end
end

private

def find_original(other)
find { |tag| tag == other }
end
end
end
end

+ 1
- 1
app/web/controllers/web/tags_controller.rb View File

@@ -37,7 +37,7 @@ module Web
@profile ||= decorate(
load(
Aggregates::Member,
Projections::Members::Query.aggregate_id_for(params[:handle]),
Projections::Members::Query.aggregate_id_for(params[:handle])
),
ViewModels::Profile,
ViewModels::Profile::NullProfile

+ 4
- 1
app/web/views/profile.erb View File

@@ -39,7 +39,10 @@
</a>

<%- profile.tags_for(current_member).each do |tag| %>
<a class="tag is-primary is-medium<%= tag.by?(current_member) ? ' mine' : '' %>" href="/tags/<%= tag.slug %>/"><%= tag.name %></a>
<a class="tag is-primary is-medium<%= tag.by?(current_member.id) ? ' mine' : '' %>"
href="/tags/<%= tag.slug %>/">
<%= tag.name %>
</a>
<%- end %>
</section>
</div>

+ 36
- 0
test/aggregates/member/tag_list_test.rb View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true

require 'test_helper'
require 'app/aggregates/member/tag_list.rb'

module Aggregates
class Member
##
# Test TagList model; the collection of tags on a member
class TagListTest < Minitest::Spec
let(:friend_by_harry) { Minitest::Mock.new }
let(:friend_by_ron) { Minitest::Mock.new }
let(:subject) { TagList.new }

it 'adds tags through <<' do
subject << friend_by_harry
assert_includes(subject, friend_by_harry)
end

it 'merges tags who are eql when adding' do
friend_by_harry.expect(:==, true, [friend_by_ron])
friend_by_harry.expect(:==, true, [friend_by_ron])

friend_by_harry.expect(:merge, friend_by_harry, [friend_by_ron])

subject << friend_by_harry
subject << friend_by_ron
assert_equal(1, subject.length)
friend_by_harry.verify
end

# TODO: implement limits.
# TODO: implement ordering.
end
end
end

+ 44
- 0
test/aggregates/member/tag_test.rb View File

@@ -0,0 +1,44 @@
# frozen_string_literal: true

require 'test_helper'
require 'app/aggregates/member/tag.rb'

module Aggregates
class Member
##
# Test Tag module under Member Aggregate
class TagTest < Minitest::Spec
let(:harry_author) { fake_uuid(Aggregates::Member, 1) }
let(:ron_author) { fake_uuid(Aggregates::Member, 2) }
let(:subject) { Tag.new('friend', harry_author) }

it 'makes authors from passed in author' do
assert_equal([harry_author], subject.authors)
end

it 'is equal when names are equal' do
assert(subject == Tag.new('friend', harry_author))
end

it 'is equal when names are equal but authors are not' do
assert(subject == Tag.new('friend', ron_author))
end

it 'is comparable to nil' do
refute_equal(nil, subject)
end

it 'appends authors on merge' do
assert_equal(
[harry_author, ron_author],
subject.merge(Tag.new('friend', ron_author)).authors
)
end

it 'by? reports true when author is amoungs authors' do
assert(subject.by?(harry_author))
refute(subject.by?(ron_author))
end
end
end
end

+ 24
- 0
test/aggregates/member_test.rb View File

@@ -26,6 +26,30 @@ module Aggregates
assert_equal(subject.handle, Handle.new('harry'))
end

it '#add_tag adds a tag' do
author_id = fake_uuid(Aggregates::Member, 2)
subject.add_tag('author_id' => author_id, 'tag' => 'friend')

assert_includes(
subject.tags_for(author_id),
Aggregates::Member::Tag.new('friend', fake_uuid(Aggregates::Member, 2))
)
end

it '#add_tag merges with tags with same names' do
author_id = fake_uuid(Aggregates::Member, 2)
subject.add_tag('author_id' => author_id, 'tag' => 'friend')

other_author_id = fake_uuid(Aggregates::Member, 3)
subject.add_tag('author_id' => other_author_id, 'tag' => 'friend')

assert_equal(subject.tags_for(author_id).length, 1)
assert_includes(
subject.tags_for(author_id).first.authors,
other_author_id
)
end

it 'MemberAdded sets added attribute to true' do
assert(Aggregates::Member.new(id, [MemberAdded.new]).attributes[:added])
end

+ 23
- 6
test/integration/web/member_tags_member_test.rb View File

@@ -27,18 +27,35 @@ class MemberTagsMemberTest < Minitest::WebSpec
assert_selector('.tag.mine', text: 'friend')
end

it 'can only add a tag once per tagging member'
it 'can add a tag multiple times on one profile'
it 'shows tags from other members styled different' do
as(hermoine)
discover_member(username: ron[:username]).upto(:profile_visited)

assert_selector('.tag', text: 'friend')
refute_selector('.tag.mine')
end

# TODO: for accountability, we need to show the tag.authors in a neat
# and friendly hover dialog.
# Then we can test that a member who tags multiple times, only appears once
# in the authors. One tag "friend" per author, so to say.

it 'profile can be tagged multiple times with one tag' do
as(hermoine)
discover_member(username: ron[:username]).upto(:profile_visited)
tags_member.upto(:tag_added)

# Tags shows up as "mine" but it still appears only once: hpotter and mine
# are merged. One .tag is used for the "add" button, making total 2.
assert_selector('.tag.mine', text: 'friend')
assert_equal(find_all('.tag').length, 2)
end

it 'follows the tagged member' do
# Determine that harry follows ron by checking the notification sent to
# ron. TODO: Change to check with my followings once we have that overview
as(ron)

# @INK: implementing a follow feature. Which:
# * sends a notification
# * introduces a Followers list.
# * adds me as follower.
main_menu('Updates').click
assert_content '@hpotter@example.com started following you'
end

+ 11
- 0
test/support/data_helpers.rb View File

@@ -45,4 +45,15 @@ module DataHelpers

ron
end

def hermoine
hermoine = {
username: 'hermoine',
email: 'hermoine@example.org',
password: 'secret'
}
member_registers(hermoine).upto(:confirmed).html

hermoine
end
end

Loading…
Cancel
Save