Skip to content

Commit 31b9bd3

Browse files
test(firebase_ui_database): update e2e tests and integrate into CI
1 parent 5afa757 commit 31b9bd3

File tree

8 files changed

+275
-6
lines changed

8 files changed

+275
-6
lines changed

database.rules.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"rules": {
3+
".read": false,
4+
".write": false,
5+
"flutter-tests": {
6+
".read": true,
7+
".write": true
8+
}
9+
}
10+
}

firebase.json

+9-3
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@
77
"predeploy": "npm --prefix \"$RESOURCE_DIR\" run build",
88
"source": "functions"
99
},
10+
"database": {
11+
"rules": "database.rules.json"
12+
},
1013
"emulators": {
1114
"firestore": {
12-
"port": "8080"
15+
"port": 8080
1316
},
1417
"auth": {
15-
"port": "9099"
18+
"port": 9099
19+
},
20+
"database": {
21+
"port": 9000
1622
},
1723
"storage": {
18-
"port": "9199"
24+
"port": 9199
1925
},
2026
"ui": {
2127
"enabled": true,

melos.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,4 @@ scripts:
147147
description: Add a license header to all necessary files.
148148

149149
emulator:start:
150-
run: firebase emulators:start --only firestore,auth,functions,storage --import=./emulators-data --export-on-exit=./emulators-data
150+
run: firebase emulators:start --only firestore,auth,functions,storage,database --import=./emulators-data --export-on-exit=./emulators-data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// Copyright 2024, the Chromium project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:firebase_database/firebase_database.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:firebase_ui_database/firebase_ui_database.dart';
9+
import 'package:mockito/mockito.dart';
10+
import '../utils.dart';
11+
12+
const _kTestPath = 'flutter-tests';
13+
14+
void main() {
15+
group('DatabaseListViewBuilder', () {
16+
setUp(() async {
17+
await clearReference(
18+
rtdb.ref(_kTestPath),
19+
);
20+
});
21+
22+
testWidgets('Allows specifying custom error handler', (tester) async {
23+
final builderSpy = ListViewBuilderSpy();
24+
final ref = rtdb.ref('unknown');
25+
26+
await tester.pumpWidget(
27+
MaterialApp(
28+
home: Scaffold(
29+
body: FirebaseDatabaseListView(
30+
query: ref,
31+
errorBuilder: (context, error, stack) {
32+
return Text('error: $error');
33+
},
34+
itemBuilder: builderSpy.call,
35+
),
36+
),
37+
),
38+
);
39+
40+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
41+
expect(find.byType(ListView), findsNothing);
42+
43+
await tester.pumpAndSettle();
44+
45+
verifyZeroInteractions(builderSpy);
46+
47+
expect(
48+
find.text(
49+
'error: [firebase_database/permission-denied] '
50+
'Client doesn\'t have permission to access the desired data.',
51+
),
52+
findsOneWidget,
53+
);
54+
expect(find.byType(ListView), findsNothing);
55+
});
56+
57+
testWidgets('Allows specifying custom loading handler', (tester) async {
58+
final ref = rtdb.ref(_kTestPath);
59+
60+
await tester.pumpWidget(
61+
MaterialApp(
62+
home: Scaffold(
63+
body: FirebaseDatabaseListView(
64+
query: ref,
65+
loadingBuilder: (context) => const Text('loading...'),
66+
itemBuilder: (context, snapshot) => throw UnimplementedError(),
67+
),
68+
),
69+
),
70+
);
71+
72+
expect(find.text('loading...'), findsOneWidget);
73+
expect(find.byType(CircularProgressIndicator), findsNothing);
74+
expect(find.byType(ListView), findsNothing);
75+
});
76+
77+
testWidgets(
78+
'By default, shows a progress indicator when loading',
79+
(tester) async {
80+
final ref = rtdb.ref(_kTestPath);
81+
82+
await tester.pumpWidget(
83+
MaterialApp(
84+
home: Scaffold(
85+
body: FirebaseDatabaseListView(
86+
query: ref,
87+
itemBuilder: (context, snapshot) => throw UnimplementedError(),
88+
),
89+
),
90+
),
91+
);
92+
93+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
94+
expect(find.byType(ListView), findsNothing);
95+
},
96+
);
97+
98+
testWidgets('By default, ignore errors', (tester) async {
99+
final builderSpy = ListViewBuilderSpy();
100+
final ref = rtdb.ref(_kTestPath);
101+
102+
await tester.pumpWidget(
103+
MaterialApp(
104+
home: Scaffold(
105+
body: FirebaseDatabaseListView(
106+
query: ref,
107+
cacheExtent: 0,
108+
itemBuilder: (context, snapshot) => throw UnimplementedError(),
109+
),
110+
),
111+
),
112+
);
113+
114+
verifyZeroInteractions(builderSpy);
115+
116+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
117+
expect(find.byType(ListView), findsNothing);
118+
119+
await ref.onValue.first.then((value) {}, onError: (_) {});
120+
121+
await tester.pump();
122+
123+
expect(find.byType(ListView), findsOneWidget);
124+
});
125+
126+
testWidgets(
127+
'When reaching the end of the list, loads more items',
128+
(tester) async {
129+
final ref = rtdb.ref().child(_kTestPath);
130+
131+
await fillReference(ref, 25);
132+
late double size;
133+
134+
await tester.pumpWidget(
135+
MaterialApp(
136+
home: Material(
137+
child: Builder(builder: (context) {
138+
final mq = MediaQuery.of(context);
139+
final h = mq.size.height;
140+
size = h / 5;
141+
142+
return FirebaseDatabaseListView(
143+
physics: const ClampingScrollPhysics(),
144+
query: ref.orderByValue(),
145+
cacheExtent: 0,
146+
pageSize: 5,
147+
itemExtent: size,
148+
itemBuilder: (context, snapshot) {
149+
final v = snapshot.value as int;
150+
151+
return Container(
152+
alignment: Alignment.center,
153+
color: Colors.black.withAlpha(v % 2 == 0 ? 50 : 100),
154+
key: ValueKey(v.toString()),
155+
child: Text(
156+
v.toString(),
157+
textAlign: TextAlign.center,
158+
),
159+
);
160+
},
161+
);
162+
}),
163+
),
164+
),
165+
);
166+
167+
await tester.pumpAndSettle();
168+
169+
for (int i = 0; i < 5; i++) {
170+
expect(find.byKey(ValueKey(i.toString())), findsOneWidget);
171+
}
172+
173+
// allow for more items to be fetcehed
174+
await Future.delayed(const Duration(milliseconds: 500));
175+
176+
await tester.drag(
177+
find.byKey(const ValueKey('4')),
178+
Offset(0, -size * 5),
179+
touchSlopY: 0,
180+
);
181+
182+
await tester.pumpAndSettle();
183+
184+
for (int i = 5; i < 9; i++) {
185+
expect(find.byKey(ValueKey(i.toString())), findsOneWidget);
186+
}
187+
188+
// allow for more items to be fetched
189+
await Future.delayed(const Duration(milliseconds: 500));
190+
191+
await tester.drag(
192+
find.byKey(const ValueKey('9')),
193+
Offset(0, -size * 5),
194+
touchSlopY: 0,
195+
);
196+
197+
await tester.pumpAndSettle();
198+
199+
for (int i = 10; i < 15; i++) {
200+
expect(find.byKey(ValueKey(i.toString())), findsOneWidget);
201+
}
202+
},
203+
);
204+
});
205+
}
206+
207+
class ListViewBuilderSpy<T> extends Mock {
208+
Widget call(
209+
BuildContext? context,
210+
T? snapshot,
211+
) {
212+
return super.noSuchMethod(
213+
Invocation.method(#call, [context, snapshot]),
214+
returnValueForMissingStub: Container(),
215+
returnValue: Container(),
216+
);
217+
}
218+
}
219+
220+
Future<void> fillReference(DatabaseReference ref, int length) {
221+
return Future.wait([
222+
for (var i = 0; i < length; i++) ref.push().set(i),
223+
]);
224+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2024, the Chromium project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
7+
import 'database_list_view_test.dart' as database_list_view_test;
8+
9+
Future<void> main() async {
10+
group('Real-time Database', () {
11+
database_list_view_test.main();
12+
});
13+
}

tests/integration_test/firebase_ui_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import './firebase_ui_oauth_google/firebase_ui_oauth_google_e2e.dart'
1717
as firebase_ui_oauth_google_e2e;
1818
import './firebase_ui_oauth_twitter/firebase_ui_oauth_twitter_e2e.dart'
1919
as firebase_ui_oauth_twitter_e2e;
20-
20+
import './firebase_ui_database/firebase_ui_database.dart' as firebase_ui_database;
2121
import 'utils.dart';
2222

2323
void main() {
@@ -32,7 +32,7 @@ void main() {
3232
firebase_ui_oauth_twitter_e2e.main();
3333
// TODO: add desktop tests
3434
}
35-
35+
firebase_ui_database.main();
3636
firebase_ui_firestore_e2e.main();
3737
});
3838
}

tests/integration_test/utils.dart

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:convert';
88
import 'package:cloud_firestore/cloud_firestore.dart';
99
import 'package:firebase_auth/firebase_auth.dart' as fba;
1010
import 'package:firebase_core/firebase_core.dart';
11+
import 'package:firebase_database/firebase_database.dart';
1112
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
1213
import 'package:flutter/foundation.dart';
1314
import 'package:flutter/material.dart';
@@ -31,6 +32,7 @@ bool get isMobile {
3132

3233
late FirebaseFirestore db;
3334
late fba.FirebaseAuth auth;
35+
late FirebaseDatabase rtdb;
3436

3537
bool _prepared = false;
3638

@@ -49,6 +51,10 @@ Future<void> prepare() async {
4951
FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
5052
db = FirebaseFirestore.instance;
5153

54+
FirebaseDatabase.instance.useDatabaseEmulator('localhost', 9000);
55+
56+
rtdb = FirebaseDatabase.instance;
57+
5258
await authCleanup();
5359
}
5460

@@ -162,6 +168,14 @@ Future<CollectionReference<T>> clearCollection<T>(
162168
return ref;
163169
}
164170

171+
Future<void> clearReference(
172+
DatabaseReference ref,
173+
) async {
174+
final snapshot = await ref.get();
175+
if (!snapshot.exists) return;
176+
await ref.remove();
177+
}
178+
165179
extension<T> on TypeMatcher<T> {
166180
TypeMatcher<T> applyHaving(
167181
String name,

tests/pubspec.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ dependencies:
2626
http: ^1.1.2
2727
google_sign_in: ^6.2.1
2828
firebase_ui_shared: ^1.4.1
29+
firebase_database: ^10.4.0
30+
firebase_ui_database: ^1.4.1
2931

3032
dev_dependencies:
3133
flutter_test:

0 commit comments

Comments
 (0)