Skip to content

Commit 6a86752

Browse files
authored
test: Run test for supabase_flutter on web as well (#1140)
* test: support web test for supabase_flutter * ci: add chrome test * ci: run chrome test only on latest version * ci: add wasm test * ci: correct matrix check * refactor: fix analytics * refactor: migrate web localstorage to web package
1 parent 1692299 commit 6a86752

File tree

6 files changed

+105
-77
lines changed

6 files changed

+105
-77
lines changed

.github/workflows/supabase_flutter.yml

+10-2
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,24 @@ jobs:
6060
melos bootstrap
6161
6262
- name: flutterfmt
63-
if: ${{ matrix.sdk == '3.x'}}
63+
if: ${{ matrix.flutter-version == '3.x'}}
6464
run: dart format lib test -l 80 --set-exit-if-changed
6565

6666
- name: analyzer
67-
if: ${{ matrix.sdk == '3.x'}}
67+
if: ${{ matrix.flutter-version == '3.x'}}
6868
run: flutter analyze --fatal-warnings --fatal-infos .
6969

7070
- name: Run tests
7171
run: flutter test --concurrency=1
7272

73+
- name: Run tests on chrome with js
74+
if: ${{ matrix.flutter-version == '3.x'}}
75+
run: flutter test --platform chrome
76+
77+
- name: Run tests on chrome with wasm
78+
if: ${{ matrix.flutter-version == '3.x'}}
79+
run: flutter test --platform chrome --wasm
80+
7381
- name: Run tests with downgraded app_links
7482
run: |
7583
flutter pub downgrade app_links

packages/supabase_flutter/example/lib/main.dart

+13-6
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ class _LoginFormState extends State<_LoginForm> {
9898
setState(() {
9999
_loading = true;
100100
});
101+
final ScaffoldMessengerState scaffoldMessenger =
102+
ScaffoldMessenger.of(context);
101103
try {
102104
final email = _emailController.text;
103105
final password = _passwordController.text;
@@ -106,7 +108,7 @@ class _LoginFormState extends State<_LoginForm> {
106108
password: password,
107109
);
108110
} catch (e) {
109-
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
111+
scaffoldMessenger.showSnackBar(const SnackBar(
110112
content: Text('Login failed'),
111113
backgroundColor: Colors.red,
112114
));
@@ -123,6 +125,8 @@ class _LoginFormState extends State<_LoginForm> {
123125
setState(() {
124126
_loading = true;
125127
});
128+
final ScaffoldMessengerState scaffoldMessenger =
129+
ScaffoldMessenger.of(context);
126130
try {
127131
final email = _emailController.text;
128132
final password = _passwordController.text;
@@ -131,7 +135,7 @@ class _LoginFormState extends State<_LoginForm> {
131135
password: password,
132136
);
133137
} catch (e) {
134-
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
138+
scaffoldMessenger.showSnackBar(const SnackBar(
135139
content: Text('Signup failed'),
136140
backgroundColor: Colors.red,
137141
));
@@ -173,6 +177,8 @@ class _ProfileFormState extends State<_ProfileForm> {
173177
}
174178

175179
Future<void> _loadProfile() async {
180+
final ScaffoldMessengerState scaffoldMessenger =
181+
ScaffoldMessenger.of(context);
176182
try {
177183
final userId = Supabase.instance.client.auth.currentUser!.id;
178184
final data = (await Supabase.instance.client
@@ -186,7 +192,7 @@ class _ProfileFormState extends State<_ProfileForm> {
186192
});
187193
}
188194
} catch (e) {
189-
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
195+
scaffoldMessenger.showSnackBar(const SnackBar(
190196
content: Text('Error occurred while getting profile'),
191197
backgroundColor: Colors.red,
192198
));
@@ -219,6 +225,8 @@ class _ProfileFormState extends State<_ProfileForm> {
219225
const SizedBox(height: 16),
220226
ElevatedButton(
221227
onPressed: () async {
228+
final ScaffoldMessengerState scaffoldMessenger =
229+
ScaffoldMessenger.of(context);
222230
try {
223231
setState(() {
224232
_loading = true;
@@ -233,13 +241,12 @@ class _ProfileFormState extends State<_ProfileForm> {
233241
'website': website,
234242
});
235243
if (mounted) {
236-
ScaffoldMessenger.of(context)
237-
.showSnackBar(const SnackBar(
244+
scaffoldMessenger.showSnackBar(const SnackBar(
238245
content: Text('Saved profile'),
239246
));
240247
}
241248
} catch (e) {
242-
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
249+
scaffoldMessenger.showSnackBar(const SnackBar(
243250
content: Text('Error saving profile'),
244251
backgroundColor: Colors.red,
245252
));
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
// ignore: avoid_web_libraries_in_flutter
2-
import 'dart:html' as html;
1+
import 'package:web/web.dart';
32

4-
final _localStorage = html.window.localStorage;
3+
final _localStorage = window.localStorage;
54

65
Future<bool> hasAccessToken(String persistSessionKey) async =>
7-
_localStorage.containsKey(persistSessionKey);
6+
_localStorage.getItem(persistSessionKey) != null;
87

98
Future<String?> accessToken(String persistSessionKey) async =>
10-
_localStorage[persistSessionKey];
9+
_localStorage.getItem(persistSessionKey);
1110

1211
Future<void> removePersistedSession(String persistSessionKey) async =>
13-
_localStorage.remove(persistSessionKey);
12+
_localStorage.removeItem(persistSessionKey);
1413

1514
Future<void> persistSession(
1615
String persistSessionKey, persistSessionString) async =>
17-
_localStorage[persistSessionKey] = persistSessionString;
16+
_localStorage.setItem(persistSessionKey, persistSessionString);

packages/supabase_flutter/pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies:
2222
path_provider: ^2.0.0
2323
shared_preferences: ^2.0.0
2424
logging: ^1.2.0
25+
web: '>=0.5.0 <2.0.0'
2526

2627
dev_dependencies:
2728
dart_jsonwebtoken: ^2.4.1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
@TestOn('!browser')
2+
3+
import 'package:app_links/app_links.dart';
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:supabase_flutter/supabase_flutter.dart';
6+
7+
import 'widget_test_stubs.dart';
8+
9+
void main() {
10+
const supabaseUrl = '';
11+
const supabaseKey = '';
12+
13+
group('Deep Link with PKCE code', () {
14+
late final PkceHttpClient pkceHttpClient;
15+
late final bool mockEventChannel;
16+
17+
/// Check if the current version of AppLinks uses an explicit call to get
18+
/// the initial link. This is only the case before version 6.0.0, where we
19+
/// can find the getInitialAppLink function.
20+
///
21+
/// CI pipeline is set so that it tests both app_links newer and older than v6.0.0
22+
bool appLinksExposesInitialLinkInStream() {
23+
try {
24+
// before app_links 6.0.0
25+
(AppLinks() as dynamic).getInitialAppLink;
26+
return false;
27+
} on NoSuchMethodError catch (_) {
28+
return true;
29+
}
30+
}
31+
32+
setUp(() async {
33+
pkceHttpClient = PkceHttpClient();
34+
35+
// Add initial deep link with a `code` parameter, use method channel if
36+
// we are in a version of AppLinks that use the explcit method for
37+
// getting the initial link. Otherwise we want to mock the event channel
38+
// and put the initial link there.
39+
mockEventChannel = appLinksExposesInitialLinkInStream();
40+
mockAppLink(
41+
mockMethodChannel: !mockEventChannel,
42+
mockEventChannel: mockEventChannel,
43+
initialLink: 'com.supabase://callback/?code=my-code-verifier',
44+
);
45+
await Supabase.initialize(
46+
url: supabaseUrl,
47+
anonKey: supabaseKey,
48+
debug: false,
49+
httpClient: pkceHttpClient,
50+
authOptions: FlutterAuthClientOptions(
51+
localStorage: MockEmptyLocalStorage(),
52+
pkceAsyncStorage: MockAsyncStorage()
53+
..setItem(
54+
key: 'supabase.auth.token-code-verifier',
55+
value: 'raw-code-verifier'),
56+
),
57+
);
58+
});
59+
60+
test(
61+
'Having `code` as the query parameter triggers `getSessionFromUrl` call on initialize',
62+
() async {
63+
// Wait for the initial app link to be handled, as this is an async
64+
// process when mocking the event channel.
65+
if (mockEventChannel) {
66+
await Future.delayed(const Duration(milliseconds: 500));
67+
}
68+
expect(pkceHttpClient.requestCount, 1);
69+
expect(pkceHttpClient.lastRequestBody['auth_code'], 'my-code-verifier');
70+
});
71+
});
72+
}

packages/supabase_flutter/test/supabase_flutter_test.dart

+3-62
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'package:app_links/app_links.dart';
21
import 'package:flutter_test/flutter_test.dart';
32
import 'package:supabase_flutter/supabase_flutter.dart';
43

@@ -79,13 +78,14 @@ void main() {
7978
authOptions: FlutterAuthClientOptions(
8079
localStorage: MockExpiredStorage(),
8180
pkceAsyncStorage: MockAsyncStorage(),
81+
autoRefreshToken: false,
8282
),
8383
);
8484
});
8585

86-
test('initial session contains the error', () async {
86+
test('emits exception when no auto refresh', () async {
8787
// Give it a delay to wait for recoverSession to throw
88-
await Future.delayed(const Duration(milliseconds: 10));
88+
await Future.delayed(const Duration(milliseconds: 100));
8989

9090
await expectLater(Supabase.instance.client.auth.onAuthStateChange,
9191
emitsError(isA<AuthException>()));
@@ -113,65 +113,6 @@ void main() {
113113
});
114114
});
115115

116-
group('Deep Link with PKCE code', () {
117-
late final PkceHttpClient pkceHttpClient;
118-
late final bool mockEventChannel;
119-
120-
/// Check if the current version of AppLinks uses an explicit call to get
121-
/// the initial link. This is only the case before version 6.0.0, where we
122-
/// can find the getInitialAppLink function.
123-
///
124-
/// CI pipeline is set so that it tests both app_links newer and older than v6.0.0
125-
bool appLinksExposesInitialLinkInStream() {
126-
try {
127-
// before app_links 6.0.0
128-
(AppLinks() as dynamic).getInitialAppLink;
129-
return false;
130-
} on NoSuchMethodError catch (_) {
131-
return true;
132-
}
133-
}
134-
135-
setUp(() async {
136-
pkceHttpClient = PkceHttpClient();
137-
138-
// Add initial deep link with a `code` parameter, use method channel if
139-
// we are in a version of AppLinks that use the explcit method for
140-
// getting the initial link. Otherwise we want to mock the event channel
141-
// and put the initial link there.
142-
mockEventChannel = appLinksExposesInitialLinkInStream();
143-
mockAppLink(
144-
mockMethodChannel: !mockEventChannel,
145-
mockEventChannel: mockEventChannel,
146-
initialLink: 'com.supabase://callback/?code=my-code-verifier',
147-
);
148-
await Supabase.initialize(
149-
url: supabaseUrl,
150-
anonKey: supabaseKey,
151-
debug: false,
152-
httpClient: pkceHttpClient,
153-
authOptions: FlutterAuthClientOptions(
154-
localStorage: MockEmptyLocalStorage(),
155-
pkceAsyncStorage: MockAsyncStorage()
156-
..setItem(
157-
key: 'supabase.auth.token-code-verifier',
158-
value: 'raw-code-verifier'),
159-
),
160-
);
161-
});
162-
163-
test(
164-
'Having `code` as the query parameter triggers `getSessionFromUrl` call on initialize',
165-
() async {
166-
// Wait for the initial app link to be handled, as this is an async
167-
// process when mocking the event channel.
168-
if (mockEventChannel) {
169-
await Future.delayed(const Duration(milliseconds: 500));
170-
}
171-
expect(pkceHttpClient.requestCount, 1);
172-
expect(pkceHttpClient.lastRequestBody['auth_code'], 'my-code-verifier');
173-
});
174-
});
175116
group('EmptyLocalStorage', () {
176117
late EmptyLocalStorage localStorage;
177118

0 commit comments

Comments
 (0)