Skip to content

Commit 93ff4ce

Browse files
authored
Adds tabs and data persistence (#219)
1 parent 03f99c4 commit 93ff4ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2089
-381
lines changed

app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ android {
2626
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
2727
}
2828
}
29+
sourceSets {
30+
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
31+
}
2932
}
3033
signingConfigs {
3134
release
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
{
2+
"formatVersion": 1,
3+
"database": {
4+
"version": 2,
5+
"identityHash": "d95e3ecc049203a49fdd433255c44af0",
6+
"entities": [
7+
{
8+
"tableName": "https_upgrade_domain",
9+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`domain` TEXT NOT NULL, PRIMARY KEY(`domain`))",
10+
"fields": [
11+
{
12+
"fieldPath": "domain",
13+
"columnName": "domain",
14+
"affinity": "TEXT",
15+
"notNull": true
16+
}
17+
],
18+
"primaryKey": {
19+
"columnNames": [
20+
"domain"
21+
],
22+
"autoGenerate": false
23+
},
24+
"indices": [],
25+
"foreignKeys": []
26+
},
27+
{
28+
"tableName": "disconnect_tracker",
29+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `category` TEXT NOT NULL, `networkName` TEXT NOT NULL, `networkUrl` TEXT NOT NULL, PRIMARY KEY(`url`))",
30+
"fields": [
31+
{
32+
"fieldPath": "url",
33+
"columnName": "url",
34+
"affinity": "TEXT",
35+
"notNull": true
36+
},
37+
{
38+
"fieldPath": "category",
39+
"columnName": "category",
40+
"affinity": "TEXT",
41+
"notNull": true
42+
},
43+
{
44+
"fieldPath": "networkName",
45+
"columnName": "networkName",
46+
"affinity": "TEXT",
47+
"notNull": true
48+
},
49+
{
50+
"fieldPath": "networkUrl",
51+
"columnName": "networkUrl",
52+
"affinity": "TEXT",
53+
"notNull": true
54+
}
55+
],
56+
"primaryKey": {
57+
"columnNames": [
58+
"url"
59+
],
60+
"autoGenerate": false
61+
},
62+
"indices": [],
63+
"foreignKeys": []
64+
},
65+
{
66+
"tableName": "network_leaderboard",
67+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkName` TEXT NOT NULL, `domainVisited` TEXT NOT NULL, PRIMARY KEY(`networkName`, `domainVisited`))",
68+
"fields": [
69+
{
70+
"fieldPath": "networkName",
71+
"columnName": "networkName",
72+
"affinity": "TEXT",
73+
"notNull": true
74+
},
75+
{
76+
"fieldPath": "domainVisited",
77+
"columnName": "domainVisited",
78+
"affinity": "TEXT",
79+
"notNull": true
80+
}
81+
],
82+
"primaryKey": {
83+
"columnNames": [
84+
"networkName",
85+
"domainVisited"
86+
],
87+
"autoGenerate": false
88+
},
89+
"indices": [],
90+
"foreignKeys": []
91+
},
92+
{
93+
"tableName": "app_configuration",
94+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `appConfigurationDownloaded` INTEGER NOT NULL, PRIMARY KEY(`key`))",
95+
"fields": [
96+
{
97+
"fieldPath": "key",
98+
"columnName": "key",
99+
"affinity": "TEXT",
100+
"notNull": true
101+
},
102+
{
103+
"fieldPath": "appConfigurationDownloaded",
104+
"columnName": "appConfigurationDownloaded",
105+
"affinity": "INTEGER",
106+
"notNull": true
107+
}
108+
],
109+
"primaryKey": {
110+
"columnNames": [
111+
"key"
112+
],
113+
"autoGenerate": false
114+
},
115+
"indices": [],
116+
"foreignKeys": []
117+
},
118+
{
119+
"tableName": "tabs",
120+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tabId` TEXT NOT NULL, `url` TEXT, `title` TEXT, PRIMARY KEY(`tabId`))",
121+
"fields": [
122+
{
123+
"fieldPath": "tabId",
124+
"columnName": "tabId",
125+
"affinity": "TEXT",
126+
"notNull": true
127+
},
128+
{
129+
"fieldPath": "url",
130+
"columnName": "url",
131+
"affinity": "TEXT",
132+
"notNull": false
133+
},
134+
{
135+
"fieldPath": "title",
136+
"columnName": "title",
137+
"affinity": "TEXT",
138+
"notNull": false
139+
}
140+
],
141+
"primaryKey": {
142+
"columnNames": [
143+
"tabId"
144+
],
145+
"autoGenerate": false
146+
},
147+
"indices": [
148+
{
149+
"name": "index_tabs_tabId",
150+
"unique": false,
151+
"columnNames": [
152+
"tabId"
153+
],
154+
"createSql": "CREATE INDEX `index_tabs_tabId` ON `${TABLE_NAME}` (`tabId`)"
155+
}
156+
],
157+
"foreignKeys": []
158+
},
159+
{
160+
"tableName": "tab_selection",
161+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `tabId` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`tabId`) REFERENCES `tabs`(`tabId`) ON UPDATE NO ACTION ON DELETE SET NULL )",
162+
"fields": [
163+
{
164+
"fieldPath": "id",
165+
"columnName": "id",
166+
"affinity": "INTEGER",
167+
"notNull": true
168+
},
169+
{
170+
"fieldPath": "tabId",
171+
"columnName": "tabId",
172+
"affinity": "TEXT",
173+
"notNull": false
174+
}
175+
],
176+
"primaryKey": {
177+
"columnNames": [
178+
"id"
179+
],
180+
"autoGenerate": false
181+
},
182+
"indices": [
183+
{
184+
"name": "index_tab_selection_tabId",
185+
"unique": false,
186+
"columnNames": [
187+
"tabId"
188+
],
189+
"createSql": "CREATE INDEX `index_tab_selection_tabId` ON `${TABLE_NAME}` (`tabId`)"
190+
}
191+
],
192+
"foreignKeys": [
193+
{
194+
"table": "tabs",
195+
"onDelete": "SET NULL",
196+
"onUpdate": "NO ACTION",
197+
"columns": [
198+
"tabId"
199+
],
200+
"referencedColumns": [
201+
"tabId"
202+
]
203+
}
204+
]
205+
},
206+
{
207+
"tableName": "bookmarks",
208+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `url` TEXT NOT NULL)",
209+
"fields": [
210+
{
211+
"fieldPath": "id",
212+
"columnName": "id",
213+
"affinity": "INTEGER",
214+
"notNull": true
215+
},
216+
{
217+
"fieldPath": "title",
218+
"columnName": "title",
219+
"affinity": "TEXT",
220+
"notNull": false
221+
},
222+
{
223+
"fieldPath": "url",
224+
"columnName": "url",
225+
"affinity": "TEXT",
226+
"notNull": true
227+
}
228+
],
229+
"primaryKey": {
230+
"columnNames": [
231+
"id"
232+
],
233+
"autoGenerate": true
234+
},
235+
"indices": [],
236+
"foreignKeys": []
237+
}
238+
],
239+
"setupQueries": [
240+
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
241+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d95e3ecc049203a49fdd433255c44af0\")"
242+
]
243+
}
244+
}

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,21 @@ import com.duckduckgo.app.bookmarks.db.BookmarksDao
3232
import com.duckduckgo.app.browser.BrowserTabViewModel.Command
3333
import com.duckduckgo.app.browser.BrowserTabViewModel.Command.DisplayMessage
3434
import com.duckduckgo.app.browser.BrowserTabViewModel.Command.Navigate
35+
import com.duckduckgo.app.browser.LongPressHandler.RequiredAction.*
3536
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
3637
import com.duckduckgo.app.global.db.AppConfigurationDao
3738
import com.duckduckgo.app.global.db.AppConfigurationEntity
3839
import com.duckduckgo.app.global.db.AppDatabase
40+
import com.duckduckgo.app.global.model.SiteFactory
3941
import com.duckduckgo.app.privacy.db.NetworkLeaderboardDao
4042
import com.duckduckgo.app.privacy.db.NetworkLeaderboardEntry
4143
import com.duckduckgo.app.privacy.db.NetworkPercent
4244
import com.duckduckgo.app.privacy.model.PrivacyGrade
4345
import com.duckduckgo.app.privacy.store.TermsOfServiceStore
4446
import com.duckduckgo.app.settings.db.SettingsDataStore
4547
import com.duckduckgo.app.statistics.api.StatisticsUpdater
46-
import com.duckduckgo.app.tabs.TabDataRepository
48+
import com.duckduckgo.app.tabs.model.TabDataRepository
49+
import com.duckduckgo.app.tabs.db.TabsDao
4750
import com.duckduckgo.app.trackerdetection.model.TrackerNetwork
4851
import com.duckduckgo.app.trackerdetection.model.TrackerNetworks
4952
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
@@ -107,10 +110,14 @@ class BrowserTabViewModelTest {
107110
@Mock
108111
private lateinit var mockOmnibarConverter: OmnibarEntryConverter
109112

113+
@Mock
114+
private lateinit var tabsDao: TabsDao
115+
110116
@Captor
111117
private lateinit var commandCaptor: ArgumentCaptor<Command>
112118

113119
private lateinit var db: AppDatabase
120+
114121
private lateinit var appConfigurationDao: AppConfigurationDao
115122

116123
private lateinit var testee: BrowserTabViewModel
@@ -124,20 +131,22 @@ class BrowserTabViewModelTest {
124131
.build()
125132
appConfigurationDao = db.appConfigurationDao()
126133

134+
val siteFactory = SiteFactory(mockTermsOfServiceStore, TrackerNetworks())
135+
127136
testee = BrowserTabViewModel(
128137
statisticsUpdater = mockStatisticsUpdater,
129138
queryUrlConverter = mockOmnibarConverter,
130139
duckDuckGoUrlDetector = DuckDuckGoUrlDetector(),
131-
termsOfServiceStore = mockTermsOfServiceStore,
132-
trackerNetworks = TrackerNetworks(),
133-
tabRepository = TabDataRepository(),
140+
siteFactory = siteFactory,
141+
tabRepository = TabDataRepository(tabsDao),
134142
networkLeaderboardDao = testNetworkLeaderboardDao,
135143
autoCompleteApi = mockAutoCompleteApi,
136144
appSettingsPreferencesStore = mockSettingsStore,
137145
bookmarksDao = bookmarksDao,
138146
longPressHandler = mockLongPressHandler,
139147
appConfigurationDao = appConfigurationDao)
140148

149+
testee.load("abc")
141150
testee.url.observeForever(mockQueryObserver)
142151
testee.command.observeForever(mockCommandObserver)
143152

@@ -153,6 +162,22 @@ class BrowserTabViewModelTest {
153162
testee.command.removeObserver(mockCommandObserver)
154163
}
155164

165+
@Test
166+
fun whenViewBecomesVisibleWithActiveSiteThenKeyboardHidden() {
167+
testee.url.value = "http://exmaple.com"
168+
testee.onViewVisible()
169+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
170+
assertTrue(commandCaptor.lastValue is Command.HideKeyboard)
171+
}
172+
173+
@Test
174+
fun whenViewBecomesVisibleWithoutActiveSiteThenKeyboardNotHidden() {
175+
testee.url.value = null
176+
testee.onViewVisible()
177+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
178+
assertTrue(commandCaptor.allValues.none { it is Command.HideKeyboard })
179+
}
180+
156181
@Test
157182
fun whenSubmittedQueryHasWhitespaceItIsTrimmed() {
158183
testee.onUserSubmittedQuery(" nytimes.com ")
@@ -432,7 +457,7 @@ class BrowserTabViewModelTest {
432457
@Test
433458
fun whenUserSelectsDownloadImageOptionFromContextMenuThenDownloadFileCommandIssued() {
434459
whenever(mockLongPressHandler.userSelectedMenuItem(anyString(), any()))
435-
.thenReturn(LongPressHandler.RequiredAction.DownloadFile("example.com"))
460+
.thenReturn(DownloadFile("example.com"))
436461

437462
val mockMenuItem : MenuItem = mock()
438463
testee.userSelectedItemFromLongPressMenu("example.com", mockMenuItem)
@@ -513,6 +538,15 @@ class BrowserTabViewModelTest {
513538
assertTrue(ultimateCommand == Command.Refresh)
514539
}
515540

541+
@Test
542+
fun whenUserSelectsOpenTabThenTabCommandSent() {
543+
whenever(mockLongPressHandler.userSelectedMenuItem(any(), any())).thenReturn(OpenInNewTab("http://example.com"))
544+
val mockMenItem: MenuItem = mock()
545+
testee.userSelectedItemFromLongPressMenu("http://example.com", mockMenItem)
546+
val command = captureCommands().value as Command.NewTab
547+
assertEquals("http://example.com", command.query)
548+
}
549+
516550
@Test
517551
fun whenUserSelectsToShareLinkThenShareLinkCommandSent() {
518552
testee.userSharingLink("foo")

0 commit comments

Comments
 (0)