browser.rb 0.16.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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +516 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +21 -0
  5. data/README.md +116 -0
  6. data/Rakefile +182 -0
  7. data/browser.rb.gemspec +54 -0
  8. data/lib/Browser/Base.rb +50 -0
  9. data/lib/Browser/Bookmark.rb +41 -0
  10. data/lib/Browser/Bookmarks.rb +112 -0
  11. data/lib/Browser/Brave.rb +16 -0
  12. data/lib/Browser/Chrome.rb +16 -0
  13. data/lib/Browser/Chromium.rb +16 -0
  14. data/lib/Browser/ChromiumBased.rb +48 -0
  15. data/lib/Browser/Firefox.rb +16 -0
  16. data/lib/Browser/FirefoxBased.rb +86 -0
  17. data/lib/Browser/History.rb +41 -0
  18. data/lib/Browser/Rendering.rb +108 -0
  19. data/lib/Browser/Safari.rb +48 -0
  20. data/lib/Browser/TorBrowser.rb +26 -0
  21. data/lib/Browser/Unreadable.rb +11 -0
  22. data/lib/Browser/VERSION.rb +6 -0
  23. data/lib/browser-classes.rb +12 -0
  24. data/lib/browser.rb +29 -0
  25. data/test/Browser/Bookmark_test.rb +72 -0
  26. data/test/Browser/Brave_test.rb +143 -0
  27. data/test/Browser/Chrome_test.rb +143 -0
  28. data/test/Browser/Chromium_test.rb +143 -0
  29. data/test/Browser/Firefox_test.rb +205 -0
  30. data/test/Browser/Rendering_test.rb +68 -0
  31. data/test/Browser/Safari_test.rb +152 -0
  32. data/test/Browser/TorBrowser_test.rb +147 -0
  33. data/test/browser_test.rb +33 -0
  34. data/test/fixtures/Brave_bookmarks.json +47 -0
  35. data/test/fixtures/ChromiumBased_history.sqlite +0 -0
  36. data/test/fixtures/Chromium_bookmarks.json +47 -0
  37. data/test/fixtures/FirefoxBased_places.sqlite +0 -0
  38. data/test/fixtures/Safari_bookmarks.plist +0 -0
  39. data/test/fixtures/Safari_history.sqlite +0 -0
  40. data/test/gemspec_test.rb +42 -0
  41. data/test/loading_test.rb +35 -0
  42. metadata +194 -0
@@ -0,0 +1,143 @@
1
+ # Chromium_test.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+ require 'rspec/expectations/minitest_integration'
9
+
10
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
11
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'browser'
14
+
15
+ describe Browser::Chromium do
16
+ let(:bookmarks_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/Chromium_bookmarks.json")}
17
+ let(:history_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/ChromiumBased_history.sqlite")}
18
+ let(:expected_history_sql){'SELECT * FROM urls ORDER BY last_visit_time DESC;'}
19
+
20
+ describe Browser::Chromium::DEFAULT_PROFILE_NAME do
21
+ let(:expected_default_profile_name){'Default'}
22
+
23
+ it "contains the correct default profile name" do
24
+ expect(Browser::Chromium::DEFAULT_PROFILE_NAME).to eq(expected_default_profile_name)
25
+ end
26
+ end
27
+
28
+ context "class methods" do
29
+ subject{Browser::Chromium}
30
+
31
+ describe '.bookmarks_location' do
32
+ let(:expected_bookmarks_location){File.expand_path('~/Library/Application Support/Chromium/Default/Bookmarks')}
33
+
34
+ it "contains the correct bookmarks location" do
35
+ expect(subject.bookmarks_location).to eq(expected_bookmarks_location)
36
+ end
37
+ end
38
+
39
+ describe '.history_location' do
40
+ let(:expected_history_location){File.expand_path('~/Library/Application Support/Chromium/Default/History')}
41
+
42
+ it "contains the correct history location" do
43
+ expect(subject.history_location).to eq(expected_history_location)
44
+ end
45
+ end
46
+
47
+ describe ".bookmarks_sql" do
48
+ it "returns the bookmarks sql" do
49
+ expect(subject.send(:bookmarks_sql)).to be_nil
50
+ end
51
+ end
52
+
53
+ describe ".history_sql" do
54
+ it "returns the history sql" do
55
+ expect(subject.send(:history_sql)).to eq(expected_history_sql)
56
+ end
57
+ end
58
+ end
59
+
60
+ context "instance methods" do
61
+ subject{Browser::Chromium.new(bookmarks_location: bookmarks_fixtures_location, history_location: history_fixtures_location)}
62
+
63
+ let(:browser_name){'Chromium'}
64
+ let(:default_profile_name){'Default'}
65
+ let(:expected_history) do
66
+ [
67
+ {'id' => 1, 'url' => 'https://www.chromium.org/chromium-projects/', 'title' => 'Home', 'visit_count' => 3, 'typed_count' => 1, 'last_visit_time' => 13403232000000000, 'hidden' => 0},
68
+ {'id' => 2, 'url' => 'chrome://welcome/', 'title' => 'Welcome', 'visit_count' => 1, 'typed_count' => 0, 'last_visit_time' => 13403231000000000, 'hidden' => 0},
69
+ ]
70
+ end
71
+
72
+ describe "#initialize" do
73
+ it "returns an instance of Browser" do
74
+ expect(subject).to be_a(Browser::Chromium)
75
+ end
76
+
77
+ it "assigns @bookmarks_location" do
78
+ expect(subject.instance_variable_get(:@bookmarks_location)).to eq(bookmarks_fixtures_location)
79
+ end
80
+
81
+ it "assigns @history_location" do
82
+ expect(subject.instance_variable_get(:@history_location)).to eq(history_fixtures_location)
83
+ end
84
+
85
+ it "takes the default profile name when none is given" do
86
+ expect(subject.profile_name).to eq(default_profile_name)
87
+ end
88
+ end
89
+
90
+ describe "#name" do
91
+ it "returns name" do
92
+ expect(subject.name).to eq(browser_name)
93
+ end
94
+ end
95
+
96
+ let(:expected_bookmarks) do
97
+ [
98
+ {title: 'Home', url: 'https://www.chromium.org/chromium-projects/', folder: 'Bookmarks Bar'},
99
+ ]
100
+ end
101
+
102
+ describe "#bookmarks" do
103
+ it "returns an instance of Browser::Bookmarks" do
104
+ expect(subject.bookmarks).to be_a(Browser::Bookmarks)
105
+ end
106
+
107
+ it "returns the bookmarks" do
108
+ expect(subject.bookmarks.to_objects).to eq(expected_bookmarks)
109
+ end
110
+ end
111
+
112
+ describe "#history" do
113
+ it "returns an instance of Browser::History" do
114
+ expect(subject.history).to be_a(Browser::History)
115
+ end
116
+
117
+ it "returns the history" do
118
+ expect(subject.history.to_json).to eq(expected_history.to_json)
119
+ end
120
+ end
121
+
122
+ describe "#readable?" do
123
+ it "is true when the data is there" do
124
+ expect(subject.bookmarks.readable?).to be(true)
125
+ expect(subject.history.readable?).to be(true)
126
+ end
127
+ end
128
+
129
+ context "WHEN the data is not there" do
130
+ subject{Browser::Chromium.new(bookmarks_location: '/nonexistent/file', history_location: '/nonexistent/file')}
131
+
132
+ it "is not readable" do
133
+ expect(subject.bookmarks.readable?).to be(false)
134
+ expect(subject.history.readable?).to be(false)
135
+ end
136
+
137
+ it "raises Browser::Unreadable" do
138
+ expect{subject.bookmarks.to_json}.to raise_error(Browser::Unreadable)
139
+ expect{subject.history.to_json}.to raise_error(Browser::Unreadable)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,205 @@
1
+ # Firefox_test.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+ require 'rspec/expectations/minitest_integration'
9
+
10
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
11
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'fileutils'
14
+ require 'tmpdir'
15
+
16
+ require 'browser'
17
+
18
+ describe Browser::Firefox do
19
+ let(:places_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/FirefoxBased_places.sqlite")}
20
+ let(:expected_bookmarks_sql){'SELECT moz_bookmarks.id, moz_bookmarks.parent, moz_bookmarks.position, moz_bookmarks.type, moz_bookmarks.title, moz_bookmarks.guid, moz_places.url FROM moz_bookmarks LEFT JOIN moz_places ON moz_bookmarks.fk = moz_places.id ORDER BY moz_bookmarks.parent, moz_bookmarks.position;'}
21
+ let(:expected_history_sql){'SELECT moz_historyvisits.id, moz_places.url, moz_places.title, moz_historyvisits.visit_date FROM moz_historyvisits LEFT JOIN moz_places ON moz_historyvisits.place_id = moz_places.id ORDER BY moz_historyvisits.visit_date DESC;'}
22
+
23
+ describe '.default_profile_name' do
24
+ let(:profiles_ini) do
25
+ <<~INI
26
+ [General]
27
+ StartWithLastProfile=0
28
+ Version=2
29
+
30
+ [Profile0]
31
+ Name=default-release
32
+ IsRelative=1
33
+ Path=Profiles/abcd1234.default-release
34
+
35
+ [InstallD7847D39F15872CF]
36
+ Default=Profiles/abcd1234.default-release
37
+ Locked=1
38
+
39
+ [Profile1]
40
+ Name=default
41
+ IsRelative=1
42
+ Path=Profiles/efgh5678.default
43
+ Default=1
44
+ INI
45
+ end
46
+
47
+ it "is the profile an [Install] section names, over the one flagged Default=1" do
48
+ expect(Browser::Firefox.default_profile_name(profiles_ini)).to eq('abcd1234.default-release')
49
+ end
50
+
51
+ it "is the profile flagged Default=1 when no [Install] section names one" do
52
+ without_install = profiles_ini.sub(/\[Install.*?\n\n/m, '')
53
+ expect(Browser::Firefox.default_profile_name(without_install)).to eq('efgh5678.default')
54
+ end
55
+
56
+ it "is the only profile when nothing names a default" do
57
+ sole = "[Profile0]\nName=default-release\nIsRelative=1\nPath=Profiles/abcd1234.default-release\n"
58
+ expect(Browser::Firefox.default_profile_name(sole)).to eq('abcd1234.default-release')
59
+ end
60
+
61
+ it "raises Browser::Unreadable when two installations each name a default" do
62
+ two = profiles_ini + "\n[Install0123456789ABCDEF]\nDefault=Profiles/efgh5678.default\n"
63
+ expect{Browser::Firefox.default_profile_name(two)}.to raise_error(Browser::Unreadable, /names 2 profiles/)
64
+ end
65
+
66
+ it "raises Browser::Unreadable when there are no profiles" do
67
+ expect{Browser::Firefox.default_profile_name("[General]\nVersion=2\n")}.to raise_error(Browser::Unreadable, /names 0 profiles/)
68
+ end
69
+ end
70
+
71
+ let(:profile_name){'abcd1234.default-release'}
72
+
73
+ context "class methods" do
74
+ subject{Browser::Firefox}
75
+
76
+ describe '.bookmarks_location' do
77
+ let(:expected_bookmarks_location){File.expand_path('~/Library/Application Support/Firefox/Profiles/abcd1234.default-release/places.sqlite')}
78
+
79
+ it "contains the correct bookmarks location" do
80
+ expect(subject.bookmarks_location(profile_name: profile_name)).to eq(expected_bookmarks_location)
81
+ end
82
+ end
83
+
84
+ describe '.history_location' do
85
+ let(:expected_history_location){File.expand_path('~/Library/Application Support/Firefox/Profiles/abcd1234.default-release/places.sqlite')}
86
+
87
+ it "contains the correct history location" do
88
+ expect(subject.history_location(profile_name: profile_name)).to eq(expected_history_location)
89
+ end
90
+ end
91
+
92
+ describe ".bookmarks_sql" do
93
+ it "returns the bookmarks sql" do
94
+ expect(subject.send(:bookmarks_sql)).to eq(expected_bookmarks_sql)
95
+ end
96
+ end
97
+
98
+ describe ".history_sql" do
99
+ it "returns the history sql" do
100
+ expect(subject.send(:history_sql)).to eq(expected_history_sql)
101
+ end
102
+ end
103
+ end
104
+
105
+ context "instance methods" do
106
+ subject{Browser::Firefox.new(bookmarks_location: places_fixtures_location, history_location: places_fixtures_location, profile_name: profile_name)}
107
+
108
+ let(:browser_name){'Firefox'}
109
+ let(:expected_history) do
110
+ [
111
+ {'id' => 1, 'url' => 'https://addons.mozilla.org/en-US/firefox/', 'title' => 'Extension Starter Pack', 'visit_date' => 1787449152741667},
112
+ ]
113
+ end
114
+
115
+ describe "#initialize" do
116
+ it "returns an instance of Browser" do
117
+ expect(subject).to be_a(Browser::Firefox)
118
+ end
119
+
120
+ it "assigns @bookmarks_location" do
121
+ expect(subject.instance_variable_get(:@bookmarks_location)).to eq(places_fixtures_location)
122
+ end
123
+
124
+ it "assigns @history_location" do
125
+ expect(subject.instance_variable_get(:@history_location)).to eq(places_fixtures_location)
126
+ end
127
+
128
+ it "assigns @profile_name" do
129
+ expect(subject.instance_variable_get(:@profile_name)).to eq(profile_name)
130
+ end
131
+ end
132
+
133
+ describe "#name" do
134
+ it "returns name" do
135
+ expect(subject.name).to eq(browser_name)
136
+ end
137
+ end
138
+
139
+ let(:expected_bookmarks) do
140
+ [
141
+ {title: 'Get Help', url: 'https://support.mozilla.org/products/firefox', folder: 'Bookmarks Menu/Mozilla Firefox'},
142
+ {title: 'Customize Firefox', url: 'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars', folder: 'Bookmarks Menu/Mozilla Firefox'},
143
+ {title: 'Get Involved', url: 'https://www.mozilla.org/contribute/', folder: 'Bookmarks Menu/Mozilla Firefox'},
144
+ {title: 'About Us', url: 'https://www.mozilla.org/about/', folder: 'Bookmarks Menu/Mozilla Firefox'},
145
+ {title: 'Extension Starter Pack', url: 'https://addons.mozilla.org/en-US/firefox/', folder: 'Bookmarks Toolbar'},
146
+ ]
147
+ end
148
+
149
+ describe "#bookmarks" do
150
+ it "returns an instance of Browser::Bookmarks" do
151
+ expect(subject.bookmarks).to be_a(Browser::Bookmarks)
152
+ end
153
+
154
+ it "returns the bookmarks" do
155
+ expect(subject.bookmarks.to_objects).to eq(expected_bookmarks)
156
+ end
157
+ end
158
+
159
+ describe "#history" do
160
+ it "returns an instance of Browser::History" do
161
+ expect(subject.history).to be_a(Browser::History)
162
+ end
163
+
164
+ it "returns the history" do
165
+ expect(subject.history.to_json).to eq(expected_history.to_json)
166
+ end
167
+ end
168
+
169
+ describe "#readable?" do
170
+ it "is true when the data is there" do
171
+ expect(subject.bookmarks.readable?).to be(true)
172
+ expect(subject.history.readable?).to be(true)
173
+ end
174
+ end
175
+
176
+ context "WHEN the data is not there" do
177
+ subject{Browser::Firefox.new(bookmarks_location: '/nonexistent/file', history_location: '/nonexistent/file')}
178
+
179
+ it "is not readable" do
180
+ expect(subject.bookmarks.readable?).to be(false)
181
+ expect(subject.history.readable?).to be(false)
182
+ end
183
+
184
+ it "raises Browser::Unreadable" do
185
+ expect{subject.bookmarks.to_json}.to raise_error(Browser::Unreadable)
186
+ expect{subject.history.to_json}.to raise_error(Browser::Unreadable)
187
+ end
188
+ end
189
+
190
+ context "WHEN the database is locked, as it is while the browser runs" do
191
+ it "is not readable" do
192
+ Dir.mktmpdir do |directory|
193
+ path = File.join(directory, 'places.sqlite')
194
+ FileUtils.cp(places_fixtures_location, path)
195
+ locker = SQLite3::Database.new(path)
196
+ locker.execute('BEGIN EXCLUSIVE')
197
+ browser = Browser::Firefox.new(bookmarks_location: path, history_location: path)
198
+ expect(browser.bookmarks.readable?).to be(false)
199
+ expect{browser.history.to_json}.to raise_error(Browser::Unreadable)
200
+ locker.close
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,68 @@
1
+ # Rendering_test.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+ require 'rspec/expectations/minitest_integration'
9
+
10
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
11
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'csv'
14
+
15
+ require 'browser'
16
+
17
+ describe Browser::Rendering do
18
+ let(:records){[{title: 'Home', url: 'https://example.com/', folder: 'Bar'}, {title: 'Docs', url: 'https://example.com/docs', folder: ''}]}
19
+ let(:column_names){%i{title url folder}}
20
+
21
+ describe Browser::Rendering::CSV do
22
+ it "renders the column names, then a row per record in their order" do
23
+ expect(Browser::Rendering::CSV.new(records, column_names).render).to eq(%Q{title,url,folder\nHome,https://example.com/,Bar\nDocs,https://example.com/docs,""\n})
24
+ end
25
+
26
+ it "renders the column names alone when there are no records" do
27
+ expect(Browser::Rendering::CSV.new([], column_names).render).to eq(%Q{title,url,folder\n})
28
+ end
29
+
30
+ it "doubles a quote within a field, so the row still parses" do
31
+ records = [{title: %Q{He said "hi", then left}, url: 'https://example.com/', folder: ''}]
32
+ rendered = Browser::Rendering::CSV.new(records, column_names).render
33
+ expect(rendered).to eq(%Q{title,url,folder\n"He said ""hi"", then left",https://example.com/,""\n})
34
+ expect(::CSV.parse(rendered).last).to eq(['He said "hi", then left', 'https://example.com/', ''])
35
+ end
36
+
37
+ it "carries a comma and a newline within a field, quoting it" do
38
+ records = [{title: "one, two\nthree", url: 'https://example.com/', folder: ''}]
39
+ rendered = Browser::Rendering::CSV.new(records, column_names).render
40
+ expect(::CSV.parse(rendered).last).to eq(["one, two\nthree", 'https://example.com/', ''])
41
+ end
42
+
43
+ it "carries a title which is not ASCII" do
44
+ records = [{title: 'Café — 日本語', url: 'https://example.com/', folder: ''}]
45
+ rendered = Browser::Rendering::CSV.new(records, column_names).render
46
+ expect(::CSV.parse(rendered).last.first).to eq('Café — 日本語')
47
+ end
48
+
49
+ it "takes the columns in the order given, whatever the records' own" do
50
+ expect(Browser::Rendering::CSV.new(records, %i{url title}).render).to eq(%Q{url,title\nhttps://example.com/,Home\nhttps://example.com/docs,Docs\n})
51
+ end
52
+ end
53
+
54
+ describe Browser::Rendering::JSON do
55
+ it "renders the records as a JSON array" do
56
+ expect(Browser::Rendering::JSON.new(records).render).to eq(records.to_json)
57
+ end
58
+ end
59
+
60
+ describe Browser::Rendering::Plist do
61
+ it "renders the records as an XML property list which reads back as they were" do
62
+ plist = Browser::Rendering::Plist.new(records).render
63
+ read_back = CFPropertyList.native_types(CFPropertyList::List.new(data: plist).value)
64
+ expect(plist).to start_with('<?xml')
65
+ expect(read_back).to eq(records.collect{|record| record.transform_keys(&:to_s)})
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,152 @@
1
+ # Safari_test.rb
2
+
3
+ gem 'minitest'
4
+ gem 'minitest-spec-context'
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest-spec-context'
8
+ require 'rspec/expectations/minitest_integration'
9
+
10
+ lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
11
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'browser'
14
+
15
+ describe Browser::Safari do
16
+ let(:bookmarks_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/Safari_bookmarks.plist")}
17
+ let(:history_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/Safari_history.sqlite")}
18
+ let(:expected_history_sql){'SELECT history_items.id, history_items.url, history_visits.visit_time FROM history_items LEFT JOIN history_visits WHERE history_items.id = history_visits.history_item ORDER BY visit_time DESC;'}
19
+
20
+ describe 'DEFAULT_PROFILE_NAME' do
21
+ it "has no default profile name" do
22
+ expect(Browser::Safari::DEFAULT_PROFILE_NAME).to be_nil
23
+ end
24
+ end
25
+
26
+ context "class methods" do
27
+ subject{Browser::Safari}
28
+
29
+ describe '.bookmarks_location' do
30
+ let(:expected_bookmarks_location){File.expand_path('~/Library/Safari/Bookmarks.plist')}
31
+
32
+ it "contains the correct bookmarks location" do
33
+ expect(subject.bookmarks_location).to eq(expected_bookmarks_location)
34
+ end
35
+ end
36
+
37
+ describe '.history_location' do
38
+ let(:expected_history_location){File.expand_path('~/Library/Safari/History.db')}
39
+
40
+ it "contains the correct history location" do
41
+ expect(subject.history_location).to eq(expected_history_location)
42
+ end
43
+ end
44
+
45
+ describe ".bookmarks_sql" do
46
+ it "returns the bookmarks sql" do
47
+ expect(subject.send(:bookmarks_sql)).to be_nil
48
+ end
49
+ end
50
+
51
+ describe ".history_sql" do
52
+ it "returns the history sql" do
53
+ expect(subject.send(:history_sql)).to eq(expected_history_sql)
54
+ end
55
+ end
56
+ end
57
+
58
+ context "instance methods" do
59
+ subject{Browser::Safari.new(bookmarks_location: bookmarks_fixtures_location, history_location: history_fixtures_location)}
60
+
61
+ let(:browser_name){'Safari'}
62
+ let(:expected_history) do
63
+ [
64
+ {'id' => 1, 'url' => 'https://www.apple.com/', 'visit_time' => 780451200.0},
65
+ {'id' => 2, 'url' => 'https://support.apple.com/', 'visit_time' => 780450000.0},
66
+ ]
67
+ end
68
+
69
+ describe "#initialize" do
70
+ it "returns an instance of Browser" do
71
+ expect(subject).to be_a(Browser::Safari)
72
+ end
73
+
74
+ it "assigns @bookmarks_location" do
75
+ expect(subject.instance_variable_get(:@bookmarks_location)).to eq(bookmarks_fixtures_location)
76
+ end
77
+
78
+ it "assigns @history_location" do
79
+ expect(subject.instance_variable_get(:@history_location)).to eq(history_fixtures_location)
80
+ end
81
+
82
+ it "assigns no @profile_name" do
83
+ expect(subject.instance_variable_get(:@profile_name)).to be_nil
84
+ end
85
+ end
86
+
87
+ describe "#name" do
88
+ it "returns name" do
89
+ expect(subject.name).to eq(browser_name)
90
+ end
91
+ end
92
+
93
+ let(:expected_bookmarks) do
94
+ [
95
+ {title: 'Apple', url: 'https://www.apple.com/', folder: 'BookmarksBar'},
96
+ {title: 'iCloud', url: 'https://www.icloud.com/', folder: 'BookmarksBar'},
97
+ {title: 'Yahoo', url: 'https://www.yahoo.com/', folder: 'BookmarksBar'},
98
+ {title: 'Bing', url: 'https://www.bing.com/', folder: 'BookmarksBar'},
99
+ {title: 'Google', url: 'https://www.google.com/?client=safari&channel=mac_bm', folder: 'BookmarksBar'},
100
+ {title: 'Wikipedia', url: 'https://www.wikipedia.org/', folder: 'BookmarksBar'},
101
+ {title: 'Facebook', url: 'https://www.facebook.com/', folder: 'BookmarksBar'},
102
+ {title: 'Twitter', url: 'https://twitter.com/', folder: 'BookmarksBar'},
103
+ {title: 'LinkedIn', url: 'https://www.linkedin.com/', folder: 'BookmarksBar'},
104
+ {title: 'The Weather Channel', url: 'https://www.weather.com/', folder: 'BookmarksBar'},
105
+ {title: 'Yelp', url: 'https://www.yelp.com/', folder: 'BookmarksBar'},
106
+ {title: 'TripAdvisor', url: 'https://www.tripadvisor.com/', folder: 'BookmarksBar'},
107
+ {title: 'Home \ Anthropic', url: 'https://www.anthropic.com/', folder: ''},
108
+ ]
109
+ end
110
+
111
+ describe "#bookmarks" do
112
+ it "returns an instance of Browser::Bookmarks" do
113
+ expect(subject.bookmarks).to be_a(Browser::Bookmarks)
114
+ end
115
+
116
+ it "returns the bookmarks" do
117
+ expect(subject.bookmarks.to_objects).to eq(expected_bookmarks)
118
+ end
119
+ end
120
+
121
+ describe "#history" do
122
+ it "returns an instance of Browser::History" do
123
+ expect(subject.history).to be_a(Browser::History)
124
+ end
125
+
126
+ it "returns the history" do
127
+ expect(subject.history.to_json).to eq(expected_history.to_json)
128
+ end
129
+ end
130
+
131
+ describe "#readable?" do
132
+ it "is true when the data is there" do
133
+ expect(subject.bookmarks.readable?).to be(true)
134
+ expect(subject.history.readable?).to be(true)
135
+ end
136
+ end
137
+
138
+ context "WHEN the data is not there" do
139
+ subject{Browser::Safari.new(bookmarks_location: '/nonexistent/file', history_location: '/nonexistent/file')}
140
+
141
+ it "is not readable" do
142
+ expect(subject.bookmarks.readable?).to be(false)
143
+ expect(subject.history.readable?).to be(false)
144
+ end
145
+
146
+ it "raises Browser::Unreadable" do
147
+ expect{subject.bookmarks.to_json}.to raise_error(Browser::Unreadable)
148
+ expect{subject.history.to_json}.to raise_error(Browser::Unreadable)
149
+ end
150
+ end
151
+ end
152
+ end