improve tab sync
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lexo_rank/lexo_rank.dart';
|
||||
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
||||
import 'package:weblibre/data/database/functions/url_functions.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart';
|
||||
|
||||
void main() {
|
||||
late TabDatabase db;
|
||||
@@ -93,14 +95,216 @@ void main() {
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test('content-state sync seeds parent for an unclaimed engine row', () async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('gecko-parent', source: TabSource.addedEvent),
|
||||
_TabFixture('child', source: TabSource.addedEvent),
|
||||
]);
|
||||
|
||||
await db.tabDao.updateTabs(null, {
|
||||
'child': _tabState('child', parentId: 'gecko-parent'),
|
||||
});
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, 'gecko-parent');
|
||||
expect(child.source, TabSource.manual);
|
||||
});
|
||||
|
||||
test(
|
||||
'content-state sync retries an unresolved engine parent when the row arrives later',
|
||||
() async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('child', source: TabSource.addedEvent),
|
||||
]);
|
||||
|
||||
final initialState = {
|
||||
'child': _tabState('child', parentId: 'late-parent'),
|
||||
};
|
||||
|
||||
await db.tabDao.updateTabs(null, initialState);
|
||||
|
||||
final unresolvedChild = await db.tabDao
|
||||
.getTabDataById('child')
|
||||
.getSingleOrNull();
|
||||
expect(unresolvedChild, isNotNull);
|
||||
expect(unresolvedChild!.parentId, isNull);
|
||||
expect(unresolvedChild.source, TabSource.addedEvent);
|
||||
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('late-parent', source: TabSource.addedEvent),
|
||||
]);
|
||||
|
||||
await db.tabDao.updateTabs(initialState, {
|
||||
'child': _tabState('child', parentId: 'late-parent'),
|
||||
'late-parent': _tabState('late-parent'),
|
||||
});
|
||||
|
||||
final resolvedChild = await db.tabDao
|
||||
.getTabDataById('child')
|
||||
.getSingleOrNull();
|
||||
expect(resolvedChild, isNotNull);
|
||||
expect(resolvedChild!.parentId, 'late-parent');
|
||||
expect(resolvedChild.source, TabSource.manual);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'content-state sync ignores parent-only changes with unresolved parents',
|
||||
() async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('child', source: TabSource.addedEvent),
|
||||
]);
|
||||
|
||||
final previousState = _tabState('child');
|
||||
|
||||
await db.tabDao.updateTabs(
|
||||
{'child': previousState},
|
||||
{'child': previousState.copyWith(parentId: 'missing-parent')},
|
||||
);
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, isNull);
|
||||
expect(child.source, TabSource.addedEvent);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'content-state sync does not overwrite a locally managed parent',
|
||||
() async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('local-parent'),
|
||||
_TabFixture('gecko-parent', source: TabSource.addedEvent),
|
||||
_TabFixture('child', parentId: 'local-parent'),
|
||||
]);
|
||||
|
||||
await db.tabDao.updateTabs(null, {
|
||||
'child': _tabState('child', parentId: 'gecko-parent'),
|
||||
});
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, 'local-parent');
|
||||
expect(child.source, TabSource.manual);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'content-state sync ignores parent-only changes for locally managed rows',
|
||||
() async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('local-parent'),
|
||||
_TabFixture('gecko-parent', source: TabSource.addedEvent),
|
||||
_TabFixture('child', parentId: 'local-parent'),
|
||||
]);
|
||||
|
||||
final previousState = _tabState('child', parentId: 'local-parent');
|
||||
|
||||
await db.tabDao.updateTabs(
|
||||
{'child': previousState},
|
||||
{'child': previousState.copyWith(parentId: 'gecko-parent')},
|
||||
);
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, 'local-parent');
|
||||
expect(child.source, TabSource.manual);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'content-state sync preserves an existing parent on engine rows',
|
||||
() async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('existing-parent', source: TabSource.addedEvent),
|
||||
_TabFixture('gecko-parent', source: TabSource.addedEvent),
|
||||
_TabFixture(
|
||||
'child',
|
||||
parentId: 'existing-parent',
|
||||
source: TabSource.addedEvent,
|
||||
),
|
||||
]);
|
||||
|
||||
await db.tabDao.updateTabs(null, {
|
||||
'child': _tabState('child', parentId: 'gecko-parent'),
|
||||
});
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, 'existing-parent');
|
||||
expect(child.source, TabSource.addedEvent);
|
||||
},
|
||||
);
|
||||
|
||||
test('reorder-only moves do not claim manual hierarchy authority', () async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('other'),
|
||||
_TabFixture('child', source: TabSource.addedEvent),
|
||||
_TabFixture('gecko-parent', source: TabSource.addedEvent),
|
||||
]);
|
||||
|
||||
await db.tabDao.reorderTabs(
|
||||
movingTabIds: const ['child'],
|
||||
previousTabId: null,
|
||||
nextTabId: 'other',
|
||||
);
|
||||
|
||||
final reorderedChild = await db.tabDao
|
||||
.getTabDataById('child')
|
||||
.getSingleOrNull();
|
||||
expect(reorderedChild, isNotNull);
|
||||
expect(reorderedChild!.source, TabSource.addedEvent);
|
||||
expect(reorderedChild.parentId, isNull);
|
||||
|
||||
await db.tabDao.updateTabs(null, {
|
||||
'child': _tabState('child', parentId: 'gecko-parent'),
|
||||
});
|
||||
|
||||
final seededChild = await db.tabDao
|
||||
.getTabDataById('child')
|
||||
.getSingleOrNull();
|
||||
expect(seededChild, isNotNull);
|
||||
expect(seededChild!.parentId, 'gecko-parent');
|
||||
expect(seededChild.source, TabSource.manual);
|
||||
});
|
||||
|
||||
test('tab-list sync preserves an existing local parent', () async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('parent'),
|
||||
_TabFixture('child', parentId: 'parent'),
|
||||
]);
|
||||
|
||||
await db.tabDao.syncTabs(retainTabIds: const ['parent', 'child']);
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, 'parent');
|
||||
});
|
||||
|
||||
test('deleting a parent rewires children to the grandparent', () async {
|
||||
await _insertTabs(db, const [
|
||||
_TabFixture('grandparent'),
|
||||
_TabFixture('parent', parentId: 'grandparent'),
|
||||
_TabFixture('child', parentId: 'parent'),
|
||||
]);
|
||||
|
||||
await db.customStatement("DELETE FROM tab WHERE id = 'parent'");
|
||||
|
||||
final child = await db.tabDao.getTabDataById('child').getSingleOrNull();
|
||||
expect(child, isNotNull);
|
||||
expect(child!.parentId, 'grandparent');
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _insertTabs(TabDatabase db, List<_TabFixture> tabs) async {
|
||||
final orderKeys = _spacedOrderKeys(tabs.length);
|
||||
|
||||
for (final (index, tab) in tabs.indexed) {
|
||||
await db.tabDao.upsertTabTransactional(
|
||||
() async => tab.id,
|
||||
await db.tabDao.insertTab(
|
||||
tab.id,
|
||||
source: tab.source,
|
||||
parentId: Value(tab.parentId),
|
||||
orderKey: Value(orderKeys[index]),
|
||||
);
|
||||
@@ -128,6 +332,15 @@ List<String> _spacedOrderKeys(int count) {
|
||||
class _TabFixture {
|
||||
final String id;
|
||||
final String? parentId;
|
||||
final TabSource source;
|
||||
|
||||
const _TabFixture(this.id, {this.parentId});
|
||||
const _TabFixture(this.id, {this.parentId, this.source = TabSource.manual});
|
||||
}
|
||||
|
||||
TabState _tabState(String id, {String? parentId}) {
|
||||
return TabState.$default(id).copyWith(
|
||||
parentId: parentId,
|
||||
url: Uri.parse('https://$id.example/'),
|
||||
title: id,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user