txgh 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +64 -0
  4. data/lib/ext/zipline/output_stream.rb +62 -0
  5. data/lib/txgh.rb +53 -0
  6. data/lib/txgh/app.rb +135 -0
  7. data/lib/txgh/category_support.rb +31 -0
  8. data/lib/txgh/config.rb +11 -0
  9. data/lib/txgh/config/config_pair.rb +36 -0
  10. data/lib/txgh/config/key_manager.rb +54 -0
  11. data/lib/txgh/config/provider_instance.rb +20 -0
  12. data/lib/txgh/config/provider_support.rb +26 -0
  13. data/lib/txgh/config/providers.rb +9 -0
  14. data/lib/txgh/config/providers/file_provider.rb +19 -0
  15. data/lib/txgh/config/providers/git_provider.rb +58 -0
  16. data/lib/txgh/config/providers/raw_provider.rb +19 -0
  17. data/lib/txgh/config/tx_config.rb +77 -0
  18. data/lib/txgh/config/tx_manager.rb +15 -0
  19. data/lib/txgh/diff_calculator.rb +90 -0
  20. data/lib/txgh/empty_resource_contents.rb +43 -0
  21. data/lib/txgh/errors.rb +9 -0
  22. data/lib/txgh/github_api.rb +83 -0
  23. data/lib/txgh/github_repo.rb +88 -0
  24. data/lib/txgh/github_request_auth.rb +28 -0
  25. data/lib/txgh/handlers.rb +12 -0
  26. data/lib/txgh/handlers/download_handler.rb +84 -0
  27. data/lib/txgh/handlers/github.rb +10 -0
  28. data/lib/txgh/handlers/github/delete_handler.rb +65 -0
  29. data/lib/txgh/handlers/github/handler.rb +20 -0
  30. data/lib/txgh/handlers/github/push_handler.rb +108 -0
  31. data/lib/txgh/handlers/github/request_handler.rb +106 -0
  32. data/lib/txgh/handlers/response.rb +17 -0
  33. data/lib/txgh/handlers/stream_response.rb +39 -0
  34. data/lib/txgh/handlers/tgz_stream_response.rb +41 -0
  35. data/lib/txgh/handlers/transifex.rb +8 -0
  36. data/lib/txgh/handlers/transifex/hook_handler.rb +77 -0
  37. data/lib/txgh/handlers/transifex/request_handler.rb +78 -0
  38. data/lib/txgh/handlers/triggers.rb +9 -0
  39. data/lib/txgh/handlers/triggers/handler.rb +66 -0
  40. data/lib/txgh/handlers/triggers/pull_handler.rb +29 -0
  41. data/lib/txgh/handlers/triggers/push_handler.rb +21 -0
  42. data/lib/txgh/handlers/zip_stream_response.rb +21 -0
  43. data/lib/txgh/merge_calculator.rb +74 -0
  44. data/lib/txgh/parse_config.rb +24 -0
  45. data/lib/txgh/resource_committer.rb +39 -0
  46. data/lib/txgh/resource_contents.rb +118 -0
  47. data/lib/txgh/resource_downloader.rb +141 -0
  48. data/lib/txgh/resource_updater.rb +104 -0
  49. data/lib/txgh/response_helpers.rb +30 -0
  50. data/lib/txgh/transifex_api.rb +165 -0
  51. data/lib/txgh/transifex_project.rb +37 -0
  52. data/lib/txgh/transifex_request_auth.rb +53 -0
  53. data/lib/txgh/tx_branch_resource.rb +59 -0
  54. data/lib/txgh/tx_logger.rb +12 -0
  55. data/lib/txgh/tx_resource.rb +66 -0
  56. data/lib/txgh/utils.rb +44 -0
  57. data/lib/txgh/version.rb +3 -0
  58. data/spec/app_spec.rb +346 -0
  59. data/spec/category_support_spec.rb +43 -0
  60. data/spec/config/config_pair_spec.rb +47 -0
  61. data/spec/config/key_manager_spec.rb +48 -0
  62. data/spec/config/provider_instance_spec.rb +30 -0
  63. data/spec/config/provider_support_spec.rb +55 -0
  64. data/spec/config/tx_config_spec.rb +49 -0
  65. data/spec/config/tx_manager_spec.rb +57 -0
  66. data/spec/diff_calculator_spec.rb +90 -0
  67. data/spec/github_api_spec.rb +148 -0
  68. data/spec/github_repo_spec.rb +178 -0
  69. data/spec/github_request_auth_spec.rb +39 -0
  70. data/spec/handlers/download_handler_spec.rb +81 -0
  71. data/spec/handlers/github/delete_handler_spec.rb +71 -0
  72. data/spec/handlers/github/push_handler_spec.rb +76 -0
  73. data/spec/handlers/tgz_stream_response_spec.rb +59 -0
  74. data/spec/handlers/transifex/hook_handler_spec.rb +115 -0
  75. data/spec/handlers/zip_stream_response_spec.rb +58 -0
  76. data/spec/helpers/github_payload_builder.rb +141 -0
  77. data/spec/helpers/integration_setup.rb +47 -0
  78. data/spec/helpers/nil_logger.rb +10 -0
  79. data/spec/helpers/standard_txgh_setup.rb +92 -0
  80. data/spec/helpers/test_provider.rb +12 -0
  81. data/spec/integration/cassettes/github_l10n_hook_endpoint.yml +536 -0
  82. data/spec/integration/cassettes/pull.yml +47 -0
  83. data/spec/integration/cassettes/push.yml +544 -0
  84. data/spec/integration/cassettes/transifex_hook_endpoint.yml +560 -0
  85. data/spec/integration/config/tx.config +10 -0
  86. data/spec/integration/hooks_spec.rb +158 -0
  87. data/spec/integration/payloads/github_postbody.json +161 -0
  88. data/spec/integration/payloads/github_postbody_l10n.json +136 -0
  89. data/spec/integration/payloads/github_postbody_release.json +136 -0
  90. data/spec/integration/triggers_spec.rb +45 -0
  91. data/spec/merge_calculator_spec.rb +112 -0
  92. data/spec/parse_config_spec.rb +52 -0
  93. data/spec/resource_committer_spec.rb +42 -0
  94. data/spec/resource_contents_spec.rb +212 -0
  95. data/spec/resource_downloader_spec.rb +205 -0
  96. data/spec/resource_updater_spec.rb +147 -0
  97. data/spec/spec_helper.rb +32 -0
  98. data/spec/transifex_api_spec.rb +345 -0
  99. data/spec/transifex_project_spec.rb +45 -0
  100. data/spec/transifex_request_auth_spec.rb +39 -0
  101. data/spec/tx_branch_resource_spec.rb +99 -0
  102. data/spec/tx_resource_spec.rb +47 -0
  103. data/spec/utils_spec.rb +58 -0
  104. data/txgh.gemspec +29 -0
  105. metadata +296 -0
@@ -0,0 +1,136 @@
1
+ {
2
+ "ref": "refs/tags/v3.0.0",
3
+ "before": "0000000000000000000000000000000000000000",
4
+ "after": "a779a46381d0d10c8e9dc390da0eaf567465cb39",
5
+ "created": true,
6
+ "deleted": false,
7
+ "forced": true,
8
+ "base_ref": "refs/heads/master",
9
+ "compare": "https://github.com/txgh-bot/txgh-test-resources/compare/v3.0.0",
10
+ "commits": [
11
+
12
+ ],
13
+ "head_commit": {
14
+ "id": "a779a46381d0d10c8e9dc390da0eaf567465cb39",
15
+ "distinct": true,
16
+ "message": "11",
17
+ "timestamp": "2015-11-18T16:18:35-08:00",
18
+ "url": "https://github.com/txgh-bot/txgh-test-resources/commit/a779a46381d0d10c8e9dc390da0eaf567465cb39",
19
+ "author": {
20
+ "name": "Txgh Bot",
21
+ "email": "txgh.bot@gmail.com",
22
+ "username": "txgh-bot"
23
+ },
24
+ "committer": {
25
+ "name": "Txgh Bot",
26
+ "email": "txgh.bot@gmail.com",
27
+ "username": "txgh-bot"
28
+ },
29
+ "added": [
30
+
31
+ ],
32
+ "removed": [
33
+
34
+ ],
35
+ "modified": [
36
+ "sample.po"
37
+ ]
38
+ },
39
+ "repository": {
40
+ "id": 41740726,
41
+ "name": "txgh-test-resources",
42
+ "full_name": "txgh-bot/txgh-test-resources",
43
+ "owner": {
44
+ "name": "txgh-bot",
45
+ "email": "txgh.bot@gmail.com"
46
+ },
47
+ "private": false,
48
+ "html_url": "https://github.com/txgh-bot/txgh-test-resources",
49
+ "description": "Translation test repo for txgh",
50
+ "fork": false,
51
+ "url": "https://github.com/txgh-bot/txgh-test-resources",
52
+ "forks_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/forks",
53
+ "keys_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/keys{/key_id}",
54
+ "collaborators_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/collaborators{/collaborator}",
55
+ "teams_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/teams",
56
+ "hooks_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/hooks",
57
+ "issue_events_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/issues/events{/number}",
58
+ "events_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/events",
59
+ "assignees_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/assignees{/user}",
60
+ "branches_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/branches{/branch}",
61
+ "tags_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/tags",
62
+ "blobs_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/git/blobs{/sha}",
63
+ "git_tags_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/git/tags{/sha}",
64
+ "git_refs_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/git/refs{/sha}",
65
+ "trees_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/git/trees{/sha}",
66
+ "statuses_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/statuses/{sha}",
67
+ "languages_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/languages",
68
+ "stargazers_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/stargazers",
69
+ "contributors_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/contributors",
70
+ "subscribers_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/subscribers",
71
+ "subscription_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/subscription",
72
+ "commits_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/commits{/sha}",
73
+ "git_commits_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/git/commits{/sha}",
74
+ "comments_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/comments{/number}",
75
+ "issue_comment_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/issues/comments{/number}",
76
+ "contents_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/contents/{+path}",
77
+ "compare_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/compare/{base}...{head}",
78
+ "merges_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/merges",
79
+ "archive_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/{archive_format}{/ref}",
80
+ "downloads_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/downloads",
81
+ "issues_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/issues{/number}",
82
+ "pulls_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/pulls{/number}",
83
+ "milestones_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/milestones{/number}",
84
+ "notifications_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/notifications{?since,all,participating}",
85
+ "labels_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/labels{/name}",
86
+ "releases_url": "https://api.github.com/repos/txgh-bot/txgh-test-resources/releases{/id}",
87
+ "created_at": 1441114620,
88
+ "updated_at": "2015-09-01T13:37:00Z",
89
+ "pushed_at": 1447892352,
90
+ "git_url": "git://github.com/txgh-bot/txgh-test-resources.git",
91
+ "ssh_url": "git@github.com:txgh-bot/txgh-test-resources.git",
92
+ "clone_url": "https://github.com/txgh-bot/txgh-test-resources.git",
93
+ "svn_url": "https://github.com/txgh-bot/txgh-test-resources",
94
+ "homepage": null,
95
+ "size": 11,
96
+ "stargazers_count": 0,
97
+ "watchers_count": 0,
98
+ "language": null,
99
+ "has_issues": true,
100
+ "has_downloads": true,
101
+ "has_wiki": true,
102
+ "has_pages": false,
103
+ "forks_count": 0,
104
+ "mirror_url": null,
105
+ "open_issues_count": 0,
106
+ "forks": 0,
107
+ "open_issues": 0,
108
+ "watchers": 0,
109
+ "default_branch": "master",
110
+ "stargazers": 0,
111
+ "master_branch": "master"
112
+ },
113
+ "pusher": {
114
+ "name": "txgh-bot",
115
+ "email": "txgh.bot@gmail.com"
116
+ },
117
+ "sender": {
118
+ "login": "txgh-bot",
119
+ "id": 974299,
120
+ "avatar_url": "https://avatars.githubusercontent.com/u/974299?v=3",
121
+ "gravatar_id": "",
122
+ "url": "https://api.github.com/users/txgh-bot",
123
+ "html_url": "https://github.com/txgh-bot",
124
+ "followers_url": "https://api.github.com/users/txgh-bot/followers",
125
+ "following_url": "https://api.github.com/users/txgh-bot/following{/other_user}",
126
+ "gists_url": "https://api.github.com/users/txgh-bot/gists{/gist_id}",
127
+ "starred_url": "https://api.github.com/users/txgh-bot/starred{/owner}{/repo}",
128
+ "subscriptions_url": "https://api.github.com/users/txgh-bot/subscriptions",
129
+ "organizations_url": "https://api.github.com/users/txgh-bot/orgs",
130
+ "repos_url": "https://api.github.com/users/txgh-bot/repos",
131
+ "events_url": "https://api.github.com/users/txgh-bot/events{/privacy}",
132
+ "received_events_url": "https://api.github.com/users/txgh-bot/received_events",
133
+ "type": "User",
134
+ "site_admin": false
135
+ }
136
+ }
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rack/test'
4
+ require 'helpers/integration_setup'
5
+
6
+ describe 'trigger integration tests', integration: true do
7
+ include Rack::Test::Methods
8
+ include IntegrationSetup
9
+
10
+ def app
11
+ @app ||= Txgh::Triggers.new
12
+ end
13
+
14
+ around(:each) do |example|
15
+ Dir.chdir('./spec/integration') do
16
+ example.run
17
+ end
18
+ end
19
+
20
+ it 'verifies the pull endpoint works' do
21
+ VCR.use_cassette('pull') do
22
+ params = {
23
+ project_slug: 'test-project-88',
24
+ resource_slug: 'samplepo',
25
+ branch: 'master'
26
+ }
27
+
28
+ patch '/pull', params
29
+ expect(last_response).to be_ok
30
+ end
31
+ end
32
+
33
+ it 'verifies the push endpoint works' do
34
+ VCR.use_cassette('push') do
35
+ params = {
36
+ project_slug: 'test-project-88',
37
+ resource_slug: 'samplepo',
38
+ branch: 'master'
39
+ }
40
+
41
+ patch '/push', params
42
+ expect(last_response).to be_ok
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ include Txgh
4
+
5
+ describe MergeCalculator do
6
+ def phrase(key, string)
7
+ { 'key' => key, 'string' => string }
8
+ end
9
+
10
+ let(:resource) do
11
+ TxResource.new(
12
+ 'project_name', 'resource_slug', 'YAML',
13
+ 'en', 'en.yml', '', 'translation_file'
14
+ )
15
+ end
16
+
17
+ let(:head_contents) do
18
+ ResourceContents.from_phrase_list(resource, head_phrases)
19
+ end
20
+
21
+ let(:diff_point_contents) do
22
+ ResourceContents.from_phrase_list(resource, diff_point_phrases)
23
+ end
24
+
25
+ let(:diff_hash) do
26
+ head_contents.diff_hash(diff_point_contents)
27
+ end
28
+
29
+ let(:merge_result) do
30
+ MergeCalculator.merge(head_contents, diff_point_contents, diff_hash)
31
+ end
32
+
33
+ context 'with phrases added to HEAD' do
34
+ let(:diff_point_phrases) do
35
+ [phrase('planet.earth', 'Human')]
36
+ end
37
+
38
+ let(:head_phrases) do
39
+ diff_point_phrases + [
40
+ phrase('planet.bajor', 'Bajoran')
41
+ ]
42
+ end
43
+
44
+ it 'includes the added phrase' do
45
+ expect(merge_result.phrases).to eq(head_phrases)
46
+ end
47
+ end
48
+
49
+ context 'with phrases removed from HEAD' do
50
+ let(:diff_point_phrases) do
51
+ head_phrases + [
52
+ phrase('planet.bajor', 'Bajoran')
53
+ ]
54
+ end
55
+
56
+ let(:head_phrases) do
57
+ [phrase('planet.earth', 'Human')]
58
+ end
59
+
60
+ it 'does not include the removed phrase' do
61
+ expect(merge_result.phrases).to eq(head_phrases)
62
+ end
63
+ end
64
+
65
+ context 'with phrases modified in HEAD' do
66
+ let(:diff_point_phrases) do
67
+ [phrase('planet.bajor', 'Cardassian')]
68
+ end
69
+
70
+ let(:head_phrases) do
71
+ [phrase('planet.bajor', 'Bajoran')]
72
+ end
73
+
74
+ it 'includes the modified phrase' do
75
+ expect(merge_result.phrases).to eq(head_phrases)
76
+ end
77
+ end
78
+
79
+ context 'with no phrases modified, added, or removed' do
80
+ let(:diff_point_phrases) do
81
+ [phrase('planet.bajor', 'Bajoran')]
82
+ end
83
+
84
+ let(:head_phrases) do
85
+ diff_point_phrases
86
+ end
87
+
88
+ it 'returns an unmodified set of phrases' do
89
+ expect(merge_result.phrases).to eq(diff_point_phrases)
90
+ end
91
+ end
92
+
93
+ context 'with an separately defined diff hash' do
94
+ let(:diff_point_phrases) do
95
+ [phrase('planet.earth', 'Human')]
96
+ end
97
+
98
+ let(:head_phrases) do
99
+ diff_point_phrases + [
100
+ phrase('planet.bajor', 'Bajoran')
101
+ ]
102
+ end
103
+
104
+ let(:diff_hash) do
105
+ { added: [], removed: [], modified: [] }
106
+ end
107
+
108
+ it 'respects the diff hash by returning an unmodified set of phrases' do
109
+ expect(merge_result.phrases).to eq(diff_point_phrases)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ include Txgh
5
+
6
+ describe Txgh::ParseConfig do
7
+ let(:contents) do
8
+ """
9
+ [header]
10
+ key = val
11
+
12
+ [header2]
13
+ key2 = val2
14
+ """
15
+ end
16
+
17
+ shared_examples 'a correct config loader' do
18
+ it 'has correctly parsed the given config' do
19
+ expect(config).to be_a(::ParseConfig)
20
+ expect(config.groups).to eq(%w(header header2))
21
+ expect(config.params).to eq(
22
+ 'header' => { 'key' => 'val' },
23
+ 'header2' => { 'key2' => 'val2' }
24
+ )
25
+ end
26
+ end
27
+
28
+ describe '.load' do
29
+ let(:config) do
30
+ Txgh::ParseConfig.load(contents)
31
+ end
32
+
33
+ it_behaves_like 'a correct config loader'
34
+ end
35
+
36
+ describe '.load_file' do
37
+ around(:each) do |example|
38
+ Tempfile.open('parseconfig-test') do |f|
39
+ f.write(contents)
40
+ f.flush
41
+ @file = f
42
+ example.run
43
+ end
44
+ end
45
+
46
+ let(:config) do
47
+ Txgh::ParseConfig.load_file(@file.path)
48
+ end
49
+
50
+ it_behaves_like 'a correct config loader'
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'helpers/standard_txgh_setup'
3
+
4
+ include Txgh
5
+
6
+ describe ResourceCommitter do
7
+ include StandardTxghSetup
8
+
9
+ let(:language) { 'es' }
10
+ let(:resource) { tx_config.resource(resource_slug, branch) }
11
+ let(:downloader) { instance_double(ResourceDownloader) }
12
+ let(:file_name) { "translations/#{language}/sample.yml" }
13
+ let(:committer) do
14
+ ResourceCommitter.new(transifex_project, github_repo, logger)
15
+ end
16
+
17
+ describe '#commit_resource' do
18
+ it 'commits translations to the git repo' do
19
+ expect(ResourceDownloader).to receive(:new).and_return(downloader)
20
+ expect(downloader).to receive(:first).and_return([file_name, :translations])
21
+
22
+ expect(github_api).to(
23
+ receive(:commit).with(
24
+ repo_name, branch, { file_name => :translations }
25
+ )
26
+ )
27
+
28
+ committer.commit_resource(resource, branch, language)
29
+ end
30
+
31
+ it "doesn't commit anything if the language is the source language" do
32
+ expect(github_api).to_not receive(:commit)
33
+ committer.commit_resource(resource, branch, resource.source_lang)
34
+ end
35
+
36
+ it "doesn't commit anything if the branch is protected" do
37
+ transifex_project.protected_branches << 'foobranch'
38
+ expect(github_api).to_not receive(:commit)
39
+ committer.commit_resource(resource, 'foobranch', resource.source_lang)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,212 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ include Txgh
5
+
6
+ describe ResourceContents do
7
+ let(:tx_resource) do
8
+ TxResource.new(
9
+ 'project_slug', 'resource_slug', 'YAML',
10
+ 'en', 'source_file', '', 'translation_file'
11
+ )
12
+ end
13
+
14
+ let(:raw_contents) do
15
+ outdent(%Q(
16
+ en:
17
+ welcome:
18
+ message: Hello!
19
+ goodbye:
20
+ message: Goodbye!
21
+ ))
22
+ end
23
+
24
+ let(:contents) do
25
+ ResourceContents.from_string(tx_resource, raw_contents)
26
+ end
27
+
28
+ describe '#phrases' do
29
+ it 'extracts phrases from the resource contents' do
30
+ expect(contents.phrases).to eq([
31
+ { 'key' => 'welcome.message', 'string' => 'Hello!' },
32
+ { 'key' => 'goodbye.message', 'string' => 'Goodbye!' }
33
+ ])
34
+ end
35
+ end
36
+
37
+ describe '#add' do
38
+ it 'adds a new phrase' do
39
+ contents.add('foo.bar', 'baz')
40
+ expect(contents.phrases).to include(
41
+ { 'key' => 'foo.bar', 'string' => 'baz' }
42
+ )
43
+ end
44
+ end
45
+
46
+ describe '#write_to' do
47
+ it 'serializes the phrases to the given stream' do
48
+ stream = StringIO.new
49
+ contents.write_to(stream)
50
+ expect(stream.string).to eq(outdent(%Q(
51
+ en:
52
+ welcome:
53
+ message: ! "Hello!"
54
+ goodbye:
55
+ message: ! "Goodbye!"
56
+ )))
57
+ end
58
+
59
+ it 'includes phrases that were added after the fact' do
60
+ contents.add('foo.bar.baz', 'boo')
61
+
62
+ stream = StringIO.new
63
+ contents.write_to(stream)
64
+
65
+ expect(stream.string).to eq(outdent(%Q(
66
+ en:
67
+ welcome:
68
+ message: ! "Hello!"
69
+ goodbye:
70
+ message: ! "Goodbye!"
71
+ foo:
72
+ bar:
73
+ baz: ! "boo"
74
+ )))
75
+ end
76
+ end
77
+
78
+ describe '#to_s' do
79
+ it 'serializes to a string' do
80
+ expect(contents.to_s).to eq(outdent(%Q(
81
+ en:
82
+ welcome:
83
+ message: ! "Hello!"
84
+ goodbye:
85
+ message: ! "Goodbye!"
86
+ )))
87
+ end
88
+ end
89
+
90
+ describe '#empty?' do
91
+ it 'returns true if the resource contains no phrases' do
92
+ contents = ResourceContents.from_phrase_list(tx_resource, [])
93
+ expect(contents).to be_empty
94
+ end
95
+
96
+ it 'returns false if the resource contains phrases' do
97
+ expect(contents).to_not be_empty
98
+ end
99
+ end
100
+
101
+ describe '#diff' do
102
+ let(:head) do
103
+ ResourceContents.from_string(tx_resource, head_contents)
104
+ end
105
+
106
+ let(:diff_point) do
107
+ ResourceContents.from_string(tx_resource, diff_point_contents)
108
+ end
109
+
110
+ let(:diff) do
111
+ head.diff(diff_point)
112
+ end
113
+
114
+ context 'with phrases added to HEAD' do
115
+ let(:head_contents) do
116
+ outdent(%Q(
117
+ en:
118
+ welcome:
119
+ message: Hello!
120
+ goodbye:
121
+ message: Goodbye!
122
+ ))
123
+ end
124
+
125
+ let(:diff_point_contents) do
126
+ outdent(%Q(
127
+ en:
128
+ welcome:
129
+ message: Hello!
130
+ ))
131
+ end
132
+
133
+ it 'includes the added phrase' do
134
+ expect(diff.phrases).to eq([
135
+ { 'key' => 'goodbye.message', 'string' => 'Goodbye!' }
136
+ ])
137
+ end
138
+
139
+ it 'returns an instance of ResourceContents' do
140
+ expect(diff).to be_a(ResourceContents)
141
+ end
142
+ end
143
+
144
+ context 'with phrases removed from HEAD' do
145
+ let(:head_contents) do
146
+ outdent(%Q(
147
+ en:
148
+ welcome: Hello
149
+ ))
150
+ end
151
+
152
+ let(:diff_point_contents) do
153
+ outdent(%Q(
154
+ en:
155
+ welcome: Hello
156
+ goodbye: Goodbye
157
+ ))
158
+ end
159
+
160
+ it 'does not include any phrases' do
161
+ expect(diff.phrases).to eq([])
162
+ end
163
+ end
164
+
165
+ context 'with phrases modified in HEAD' do
166
+ let(:head_contents) do
167
+ outdent(%Q(
168
+ en:
169
+ welcome: Hello world
170
+ goodbye: Goodbye
171
+ ))
172
+ end
173
+
174
+ let(:diff_point_contents) do
175
+ outdent(%Q(
176
+ en:
177
+ welcome: Hello
178
+ goodbye: Goodbye
179
+ ))
180
+ end
181
+
182
+ it 'includes the modified phrase' do
183
+ expect(diff.phrases).to eq([
184
+ { 'key' => 'welcome', 'string' => 'Hello world' }
185
+ ])
186
+ end
187
+ end
188
+
189
+ context 'with no modifications or additions' do
190
+ let(:head_contents) do
191
+ outdent(%Q(
192
+ en:
193
+ welcome: Hello
194
+ goodbye: Goodbye
195
+ ))
196
+ end
197
+
198
+ let(:diff_point_contents) do
199
+ outdent(%Q(
200
+ en:
201
+ welcome: Hello
202
+ goodbye: Goodbye
203
+ ))
204
+ end
205
+
206
+ it 'hands back an empty diff' do
207
+ expect(diff.phrases).to eq([])
208
+ end
209
+ end
210
+
211
+ end
212
+ end