gitsu 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.licenseignore +2 -0
  3. data/.simplecov +16 -0
  4. data/.travis.yml +19 -2
  5. data/Gemfile +17 -1
  6. data/README.md +36 -7
  7. data/Rakefile +37 -3
  8. data/TODO +4 -2
  9. data/bin/git-su +15 -0
  10. data/bin/git-whoami +15 -0
  11. data/features/add_user.feature +16 -0
  12. data/features/change_user_in_different_scopes.feature +16 -0
  13. data/features/clear_user.feature +16 -0
  14. data/features/configure_default_scope.feature +22 -2
  15. data/features/edit_config.feature +16 -0
  16. data/features/list_users.feature +16 -0
  17. data/features/print_current_user.feature +16 -0
  18. data/features/print_options.feature +16 -0
  19. data/features/step_definitions/gitsu_steps.rb +33 -12
  20. data/features/support/env.rb +16 -0
  21. data/features/switch_to_fully_qualified_user.feature +21 -0
  22. data/features/switch_to_multiple_users.feature +64 -0
  23. data/features/switch_to_stored_user.feature +16 -0
  24. data/gitsu.gemspec +20 -3
  25. data/lib/gitsu.rb +17 -0
  26. data/lib/gitsu/array.rb +31 -0
  27. data/lib/gitsu/config_repository.rb +49 -0
  28. data/lib/gitsu/factory.rb +26 -3
  29. data/lib/gitsu/git.rb +44 -35
  30. data/lib/gitsu/gitsu.rb +27 -3
  31. data/lib/gitsu/runner.rb +16 -0
  32. data/lib/gitsu/shell.rb +17 -2
  33. data/lib/gitsu/switcher.rb +68 -30
  34. data/lib/gitsu/user.rb +80 -11
  35. data/lib/gitsu/user_file.rb +18 -2
  36. data/lib/gitsu/user_list.rb +68 -12
  37. data/lib/gitsu/version.rb +17 -1
  38. data/man/git-su.1.ronn +36 -4
  39. data/spec/gitsu/array_spec.rb +71 -0
  40. data/spec/gitsu/bin_spec.rb +16 -0
  41. data/spec/gitsu/config_repository_spec.rb +67 -0
  42. data/spec/gitsu/git_spec.rb +36 -15
  43. data/spec/gitsu/gitsu_spec.rb +41 -18
  44. data/spec/gitsu/runner_spec.rb +16 -0
  45. data/spec/gitsu/shell_spec.rb +55 -0
  46. data/spec/gitsu/switcher_spec.rb +53 -13
  47. data/spec/gitsu/user_file_spec.rb +91 -0
  48. data/spec/gitsu/user_list_spec.rb +112 -41
  49. data/spec/gitsu/user_spec.rb +97 -0
  50. data/spec/gitsu/version_spec.rb +16 -0
  51. data/spec/spec_helper.rb +16 -0
  52. metadata +45 -28
@@ -0,0 +1,55 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'spec_helper'
18
+
19
+ module GitSu
20
+ describe Shell do
21
+ let(:shell) { Shell.new }
22
+
23
+ describe "#capture" do
24
+ it "executes the command and returns the output" do
25
+ output = shell.capture 'echo hello'
26
+ output.should == "hello"
27
+ end
28
+ it "calls the supplied block with the exit status and output" do
29
+ command = 'echo hello && false'
30
+ block_called = false
31
+ return_value = shell.capture(command) do |output, result|
32
+ block_called = true
33
+ output.should == "hello"
34
+ result.exitstatus.should be 1
35
+ end
36
+ return_value.should == "hello"
37
+ block_called.should be true
38
+ end
39
+ end
40
+
41
+ describe "#execute" do
42
+ context "when a command returns zero" do
43
+ it "executes the command and returns true" do
44
+ shell.execute("true").should be true
45
+ end
46
+ end
47
+ context "when a command returns nonzero" do
48
+ it "executes the command and returns false" do
49
+ shell.execute("false").should be false
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -1,30 +1,68 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  require 'spec_helper'
2
18
 
3
19
  module GitSu
4
20
 
5
21
  describe Switcher do
22
+ let(:config_repository) { double('config_repository') }
6
23
  let(:git) { double('git') }
7
24
  let(:user_list) { double('user_list') }
8
25
  let(:output) { double('output').as_null_object }
9
- let(:switcher) { Switcher.new(git, user_list, output) }
10
- let(:a_user) { User.new("Johnny Bravo", "jbravo@example.com") }
26
+ let(:switcher) { Switcher.new(config_repository, git, user_list, output) }
11
27
 
12
28
  describe '#request' do
13
29
  context "when request is a fully-qualified user string (e.g. 'John Galt <jgalt@example.com>'" do
14
- it "switches to user" do
30
+ it "switches to user and adds them" do
15
31
  user = User.new('John Galt', 'jgalt@example.com')
32
+ config_repository.should_receive(:group_email_address).and_return("dev@example.com")
16
33
  git.should_receive(:select_user).with(user, :global)
17
34
  git.should_receive(:render).with(user).and_return(user.to_s)
18
35
  output.should_receive(:puts).with("Switched global user to John Galt <jgalt@example.com>")
19
- switcher.request('John Galt <jgalt@example.com>', :global)
36
+ user_list.should_receive(:list).and_return []
37
+ user_list.should_receive(:add).with(user)
38
+ switcher.request(:global, 'John Galt <jgalt@example.com>')
39
+ end
40
+ end
41
+
42
+ context "when request is for multiple users" do
43
+ it "combines users" do
44
+ combined_user = User.new('Johnny A, Johnny B and Johnny C', 'dev+a+b+c@example.com')
45
+ config_repository.should_receive(:group_email_address).and_return("dev@example.com")
46
+ git.should_receive(:select_user).with(combined_user, :global)
47
+ git.should_receive(:render).with(combined_user).and_return(combined_user.to_s)
48
+ output.should_receive(:puts).with("Switched global user to Johnny A, Johnny B and Johnny C <dev+a+b+c@example.com>")
49
+
50
+ # TODO: how do you do times in rspec again?
51
+ user_list.should_receive(:list).and_return []
52
+ user_list.should_receive(:add).with User.new("Johnny A", "a@example.com")
53
+ user_list.should_receive(:list).and_return []
54
+ user_list.should_receive(:add).with User.new("Johnny B", "b@example.com")
55
+ user_list.should_receive(:list).and_return []
56
+ user_list.should_receive(:add).with User.new("Johnny C", "c@example.com")
57
+ switcher.request(:global, 'Johnny C <c@example.com>', 'Johnny B <b@example.com>', 'Johnny A <a@example.com>')
20
58
  end
21
59
  end
22
60
 
23
- context "when no matching user found" do
61
+ context "when at least one user isn't found" do
24
62
  it "does not switch user" do
25
- user_list.should_receive(:find).with('asdfasdf').and_return User::NONE
26
- output.should_receive(:puts).with("No user found matching 'asdfasdf'")
27
- switcher.request('asdfasdf', :global)
63
+ user_list.should_receive(:find).with('john', 'xx').and_raise "No user found matching 'xx'"
64
+ output.should_receive(:puts).with("No user found matching 'xx'")
65
+ switcher.request(:global, 'john', 'xx')
28
66
  end
29
67
  end
30
68
 
@@ -32,11 +70,12 @@ module GitSu
32
70
  it "switches to requested user" do
33
71
  user = User.new('John Galt', 'jgalt@example.com')
34
72
 
35
- user_list.should_receive(:find).with("john").and_return user
73
+ user_list.should_receive(:find).with("john").and_return [user ]
74
+ config_repository.should_receive(:group_email_address).and_return("dev@example.com")
36
75
  git.should_receive(:select_user).with user, :global
37
76
  git.should_receive(:render).with(user).and_return user.to_s
38
77
  output.should_receive(:puts).with("Switched global user to John Galt <jgalt@example.com>")
39
- switcher.request "john", :global
78
+ switcher.request :global, "john"
40
79
  end
41
80
  end
42
81
 
@@ -44,12 +83,13 @@ module GitSu
44
83
  it "switches user in configured default scope" do
45
84
  user = User.new('John Galt', 'jgalt@example.com')
46
85
 
47
- user_list.should_receive(:find).with("john").and_return user
48
- git.should_receive(:default_select_scope).and_return(:global)
86
+ user_list.should_receive(:find).with("john").and_return [user ]
87
+ config_repository.should_receive(:group_email_address).and_return("dev@example.com")
88
+ config_repository.should_receive(:default_select_scope).and_return(:global)
49
89
  git.should_receive(:select_user).with user, :global
50
90
  git.should_receive(:render).with(user).and_return user.to_s
51
91
  output.should_receive(:puts).with("Switched global user to John Galt <jgalt@example.com>")
52
- switcher.request "john", :default
92
+ switcher.request :default, "john"
53
93
  end
54
94
  end
55
95
  end
@@ -0,0 +1,91 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'spec_helper'
18
+ require 'yaml'
19
+ require 'fileutils'
20
+
21
+ module GitSu
22
+ describe UserFile do
23
+ let(:user_file_name) { "/tmp/#{rand}" }
24
+ let(:user_file) { UserFile.new user_file_name }
25
+
26
+ after do
27
+ File.delete user_file_name if File.exist? user_file_name
28
+ end
29
+
30
+ describe "#initialize" do
31
+ context "when file is missing" do
32
+ it "creates the file" do
33
+ UserFile.new user_file_name
34
+ File.should exist user_file_name
35
+ end
36
+ end
37
+ context "when file is empty" do
38
+ it "writes a newline to the file" do
39
+ FileUtils.touch user_file_name
40
+ UserFile.new user_file_name
41
+ File.read(user_file_name).should == "\n"
42
+ end
43
+ end
44
+ context "otherwise" do
45
+ it "leaves the file alone" do
46
+ File.open(user_file_name, "w") {|f| f << "content" }
47
+ UserFile.new user_file_name
48
+ File.read(user_file_name).should == "content"
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#write" do
54
+ it "adds a user to the user file as YAML" do
55
+ user_file.write User.new("John Galt", "jgalt@example.com")
56
+
57
+ user_map = YAML.load_file(user_file_name)
58
+ user_map.should == { "jgalt@example.com" => "John Galt" }
59
+ end
60
+ it "doesn't overwrite existing users" do
61
+ user_file.write User.new("John Galt", "jgalt@example.com")
62
+ user_file.write User.new("Joseph Porter", "porter@example.com")
63
+
64
+ user_map = YAML.load_file(user_file_name)
65
+ user_map.should == {
66
+ "jgalt@example.com" => "John Galt",
67
+ "porter@example.com" => "Joseph Porter"
68
+ }
69
+ end
70
+ end
71
+
72
+ describe "#read" do
73
+ it "parses user file as a list of users" do
74
+ user_1 = User.new("John Galt", "jgalt@example.com")
75
+ user_2 = User.new("Joseph Porter", "porter@example.com")
76
+ user_file.write user_1
77
+ user_file.write user_2
78
+
79
+ user_map = user_file.read
80
+ user_map.sort_by {|user| user.name}.should == [ user_1, user_2 ]
81
+ end
82
+
83
+ context "when the file is empty" do
84
+ it "returns an empty list" do
85
+ user_file.read.should == []
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
@@ -1,81 +1,152 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  require 'spec_helper'
2
- require 'yaml'
3
18
 
4
19
  module GitSu
5
20
  describe UserList do
6
21
 
7
- let(:user_file) { "/tmp/#{rand}" }
22
+ let(:user_file) { InMemoryUserFile.new }
8
23
  let(:user_list) { UserList.new(user_file) }
9
- after { File.delete user_file }
10
24
 
11
25
  describe "#add" do
12
- it "adds a user to the list" do
13
- user_list.add User.new("John Galt", "jgalt@example.com")
14
-
15
- user_map = YAML.parse_file(user_file).transform
16
- user_map.keys.should include("jgalt@example.com")
17
- user_map["jgalt@example.com"].should == "John Galt"
26
+ it "adds a user to the user file" do
27
+ user = User.new("John Galt", "jgalt@example.com")
28
+ user_list.add user
29
+ user_file.should include user
18
30
  end
19
31
  end
20
32
 
21
33
  describe "#list" do
22
- context "when there are no users configured" do
23
- it "returns an empty array" do
24
- user_list.list.should be_empty
25
- end
26
- end
27
-
28
- context "when there are users configured" do
29
- it "returns an array of all the users" do
30
- user_list.add User.new("John Galt", "jgalt@example.com")
31
-
32
- list = user_list.list
33
- list.size.should be 1
34
- list.should include User.new("John Galt", "jgalt@example.com")
35
- end
34
+ it "reads all users from the user file" do
35
+ user_file.stub(:read).and_return ["user1", "user2", "user3"]
36
+ user_list.list.should == ["user1", "user2", "user3"]
36
37
  end
37
38
  end
38
39
 
39
40
  describe "#find" do
40
41
  context "when a matching user exists" do
41
42
  it "returns the matching user by first name" do
42
- user_list.add User.new("John Galt", "jgalt@example.com")
43
+ user_file.add("John Galt", "jgalt@example.com")
43
44
 
44
- user_list.find("john").should == User.new("John Galt", "jgalt@example.com")
45
- user_list.find("John").should == User.new("John Galt", "jgalt@example.com")
45
+ user_list.find("john").should == [User.new("John Galt", "jgalt@example.com")]
46
+ user_list.find("John").should == [User.new("John Galt", "jgalt@example.com")]
46
47
  end
47
48
  it "returns the matching user by email snippet" do
48
- user_list.add User.new("John Galt", "jgalt@example.com")
49
+ user_file.add("John Galt", "jgalt@example.com")
49
50
 
50
- user_list.find("example").should == User.new("John Galt", "jgalt@example.com")
51
+ user_list.find("example").should == [User.new("John Galt", "jgalt@example.com")]
51
52
  end
52
53
  it "returns the matching user by initials" do
53
- user_list.add User.new("John Galt", "john.galt@example.com")
54
+ user_file.add("John Galt", "john@example.com")
54
55
 
55
- user_list.find("jg").should == User.new("John Galt", "john.galt@example.com")
56
- user_list.find("JG").should == User.new("John Galt", "john.galt@example.com")
56
+ user_list.find("jg").should == [User.new("John Galt", "john@example.com")]
57
+ user_list.find("JG").should == [User.new("John Galt", "john@example.com")]
57
58
  end
58
59
  it "favours prefix over innards" do
59
- user_list.add User.new("Matthew Jackson", "mj@example.com")
60
- user_list.add User.new("Thomas Hickleton", "tom@example.com")
61
- user_list.add User.new("John Smith", "js@example.com")
60
+ user_file.add("Matthew Jackson", "mj@example.com")
61
+ user_file.add("Thomas Hickleton", "tom@example.com")
62
+ user_file.add("John Smith", "js@example.com")
62
63
 
63
- user_list.find("th").should eq User.new("Thomas Hickleton", "tom@example.com")
64
- user_list.find("s").should eq User.new("John Smith", "js@example.com")
64
+ user_list.find("th").should == [User.new("Thomas Hickleton", "tom@example.com")]
65
+ user_list.find("s").should == [User.new("John Smith", "js@example.com")]
65
66
  end
66
67
  it "favours full firstname match over partial one" do
67
- user_list.add User.new("Matthew Jackson", "mj@example.com")
68
- user_list.add User.new("Mat Jackson", "zz@example.com")
68
+ user_file.add("Matthew Jackson", "mj@example.com")
69
+ user_file.add("Mat Jackson", "zz@example.com")
69
70
 
70
- user_list.find("mat").should eq User.new("Mat Jackson", "zz@example.com")
71
+ user_list.find("mat").should == [User.new("Mat Jackson", "zz@example.com")]
71
72
  end
72
73
  end
73
74
 
74
75
  context "when no matching user exists" do
75
- it "returns no user" do
76
- user_list.find("john").should be User::NONE
76
+ it "raises an error" do
77
+ expect {user_list.find("john")}.to raise_error "No user found matching 'john'"
77
78
  end
78
79
  end
80
+
81
+ context "when searching for two strings" do
82
+ it "returns two users if they both match the search strings" do
83
+ user_file.add("Johnny A", "ja@example.com")
84
+ user_file.add("Johnny B", "jb@example.com")
85
+
86
+ user_list.find("ja", "jb").should == [
87
+ User.new("Johnny A", "ja@example.com"),
88
+ User.new("Johnny B", "jb@example.com")
89
+ ]
90
+ end
91
+ it "returns other matched users when first match already found" do
92
+ user_file.add("Johnny A", "ja@example.com")
93
+ user_file.add("Johnny B", "jb@example.com")
94
+
95
+ user_list.find("j", "j").should == [
96
+ User.new("Johnny A", "ja@example.com"),
97
+ User.new("Johnny B", "jb@example.com")
98
+ ]
99
+ end
100
+
101
+ context "when no matching user exists for a search term" do
102
+ it "raises an error" do
103
+ user_file.add("Johnny A", "ja@example.com")
104
+
105
+ expect {user_list.find("ja", "jb")}.to raise_error "No user found matching 'jb'"
106
+ end
107
+ end
108
+ context "when a search term only matches users that were already matched" do
109
+ it "raises an error" do
110
+ user_file.add("Johnny A", "ja@example.com")
111
+ user_file.add("Johnny B", "jb@example.com")
112
+
113
+ expect {user_list.find("ja", "jb", "j")}.to raise_error "Couldn't find a combination of unique users matching 'ja', 'jb' and 'j'"
114
+ end
115
+ end
116
+ context "when a search term only matches a user that was already matched, but the matching search term matched multiple users" do
117
+ it "adjusts the selection so that both terms match" do
118
+ user_file.add("Johnny A", "ja@example.com")
119
+ user_file.add("Johnny B", "jb@example.com")
120
+
121
+ user_list.find("j", "ja").sort_by{|u| u.name}.should == [
122
+ User.new("Johnny A", "ja@example.com"),
123
+ User.new("Johnny B", "jb@example.com")
124
+ ]
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ class InMemoryUserFile
132
+ def add(name, email)
133
+ write User.new(name, email)
134
+ end
135
+
136
+ def users
137
+ @users ||= []
138
+ end
139
+
140
+ def read
141
+ users
142
+ end
143
+
144
+ def write(user)
145
+ users << user
146
+ end
147
+
148
+ def include?(user)
149
+ users.include? user
79
150
  end
80
151
  end
81
152
  end
@@ -1,3 +1,19 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  module GitSu
2
18
  describe User do
3
19
  describe "#parse" do
@@ -23,11 +39,92 @@ module GitSu
23
39
  end
24
40
  end
25
41
 
42
+ describe "#hash" do
43
+ it "is different if name is different" do
44
+ user_a = User.new("a", "a@a.com")
45
+ user_b = User.new("b", "a@a.com")
46
+ user_a.hash.should_not == user_b.hash
47
+ end
48
+ it "is different if email is different" do
49
+ user_a = User.new("a", "a@a.com")
50
+ user_b = User.new("a", "b@a.com")
51
+ user_a.hash.should_not == user_b.hash
52
+ end
53
+ it "is the same if name and email are the same" do
54
+ user_a = User.new("a", "a@a.com")
55
+ user_b = User.new("a", "a@a.com")
56
+ user_a.hash.should == user_b.hash
57
+ end
58
+ end
59
+
26
60
  describe "#to_ansi_s" do
27
61
  it "returns a colored string representation of the user" do
28
62
  user = User.new("John Galt", "jg@example.com")
29
63
  user.to_ansi_s("\e[34m", "\e[35m", "\e[0m").should == "\e[34mJohn Galt\e[0m \e[35m<jg@example.com>\e[0m"
30
64
  end
65
+
66
+ context "when called on the null user" do
67
+ it "returns a colored string representation of the null user" do
68
+ user = User::NONE
69
+ user.to_ansi_s("\e[34m", "\e[35m", "\e[0m").should == "\e[34m(none)\e[0m"
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "#combine" do
75
+ it "combines the user with the provided other user" do
76
+ user = User.new("John A", "ja@example.com")
77
+ other = User.new("John B", "jb@example.com")
78
+ combined_user = user.combine other, "dev@example.com"
79
+ combined_user.name.should == "John A and John B"
80
+ combined_user.email.should == "dev+ja+jb@example.com"
81
+ end
82
+ it "accumulates users" do
83
+ user1 = User.new("John A", "ja@example.com")
84
+ user2 = User.new("John B", "jb@example.com")
85
+ user3 = User.new("John C", "jc@example.com")
86
+ combined_user = user1.combine(user2, "dev@example.com").combine(user3, "dev@example.com")
87
+ combined_user.name.should == "John A, John B and John C"
88
+ combined_user.email.should == "dev+ja+jb+jc@example.com"
89
+ end
90
+ it "can be called with combined users" do
91
+ user1 = User.new("John A", "ja@example.com")
92
+ user2 = User.new("John B", "jb@example.com")
93
+ user3 = User.new("John C", "jc@example.com")
94
+ combined_user = user1.combine(user2, "dev@example.com")
95
+ combined_user = user3.combine(combined_user, "dev@example.com")
96
+ combined_user.name.should == "John A, John B and John C"
97
+ combined_user.email.should == "dev+ja+jb+jc@example.com"
98
+ end
99
+ it "removes duplicate users by email" do
100
+ user = User.new("John A", "ja@example.com")
101
+ other = User.new("John B", "ja@example.com")
102
+ combined_user = user.combine other, "dev@example.com"
103
+ combined_user.name.should == "John B"
104
+ combined_user.email.should == "ja@example.com"
105
+ end
106
+ it "sorts combined users by email" do
107
+ user1 = User.new("John Z", "ja@example.com")
108
+ user2 = User.new("John X", "jc@example.com")
109
+ user3 = User.new("John Y", "jb@example.com")
110
+ combined_user = user1.combine(user2, "dev@example.com").combine(user3, "dev@example.com")
111
+ combined_user.name.should == "John Z, John Y and John X"
112
+ combined_user.email.should == "dev+ja+jb+jc@example.com"
113
+ end
114
+ it "doesn't have side-effects" do
115
+ ja = User.new("John A", "ja@example.com")
116
+ jb = User.new("John B", "jb@example.com")
117
+ combined_user = ja.combine(jb, "dev@example.com")
118
+ ja.name.should == "John A"
119
+ ja.email.should == "ja@example.com"
120
+ end
121
+ context "when combined with NONE" do
122
+ it "returns a clone of itself" do
123
+ ja = User.new("John A", "ja@example.com")
124
+ ja.combine(User::NONE, "dev@example.com").should == ja
125
+ User::NONE.combine(ja, "dev@example.com").should == ja
126
+ end
127
+ end
31
128
  end
32
129
  end
33
130
  end