@ufjs/webview 0.1.3 → 0.1.4

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.
@@ -1,369 +0,0 @@
1
- // The app half of specs/013-web-view. The web twin is
2
- // ../../test/web-view-web.test.ts, and the payload strings asserted here are
3
- // the same ones it asserts — that pair IS the "two ends, one contract" check.
4
- // EagerGestureRecognizer lives here, not in material.dart — without this the
5
- // whole file failed to compile, which `flutter test` reports as one failed
6
- // "loading …" case rather than as a missing import.
7
- import 'package:flutter/gestures.dart';
8
- import 'package:flutter/material.dart';
9
- import 'package:flutter_test/flutter_test.dart';
10
- import 'package:flutter_fjs/flutter_fjs.dart';
11
- import 'package:fjs_webview/fjs_webview.dart';
12
- import 'package:webview_flutter/webview_flutter.dart';
13
- import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
14
-
15
- MirrorNode nodeWith(Map<String, Object?> props) {
16
- final node = MirrorNode(7, 'web-view');
17
- node.props = props;
18
- return node;
19
- }
20
-
21
- void main() {
22
- setUp(resetFjsWebViewWarnings);
23
-
24
- group('payloads mirror the JS side', () {
25
- test('load, error and message', () {
26
- expect(
27
- fjsWebViewLoadPayload('https://example.com/a'),
28
- '{"src":"https://example.com/a"}',
29
- );
30
- expect(
31
- fjsWebViewErrorPayload('https://example.com/a'),
32
- '{"src":"https://example.com/a","errMsg":"web-view load failed"}',
33
- );
34
- expect(fjsWebViewMessagePayload('hello #1'), '{"data":"hello #1"}');
35
- });
36
-
37
- test('escapes what JSON has to escape', () {
38
- expect(fjsWebViewMessagePayload('a "b"\nc'), '{"data":"a \\"b\\"\\nc"}');
39
- expect(fjsWebViewMessagePayload('tab\there'), '{"data":"tab\\there"}');
40
- });
41
- });
42
-
43
- group('src', () {
44
- test('classifies the loadable schemes and refuses the rest', () {
45
- expect(fjsClassifyWebViewSrc('https://a'), FjsWebViewSrcKind.http);
46
- expect(fjsClassifyWebViewSrc('http://a'), FjsWebViewSrcKind.http);
47
- expect(fjsClassifyWebViewSrc('asset://a.html'), FjsWebViewSrcKind.asset);
48
- expect(fjsClassifyWebViewSrc(''), FjsWebViewSrcKind.empty);
49
- expect(fjsClassifyWebViewSrc(null), FjsWebViewSrcKind.empty);
50
- for (final src in const [
51
- 'file:///etc/passwd',
52
- 'javascript:alert(1)',
53
- 'data:text/html,x',
54
- 'example.com',
55
- ]) {
56
- expect(fjsClassifyWebViewSrc(src), FjsWebViewSrcKind.unsupported,
57
- reason: src);
58
- }
59
- });
60
-
61
- test('an asset path cannot escape the module directory', () {
62
- expect(fjsWebViewAssetPath('asset://demo.html'), 'demo.html');
63
- expect(fjsWebViewAssetPath('asset:///demo.html'), 'demo.html');
64
- expect(fjsWebViewAssetPath('asset://../secret'), isNull);
65
- expect(fjsWebViewAssetPath('asset://'), isNull);
66
- });
67
-
68
- test('asset:// goes to the dev server while fjs dev is connected', () {
69
- final target = fjsResolveWebViewSrc(
70
- 'asset://demo.html',
71
- devUri: Uri.parse('http://127.0.0.1:38900/'),
72
- );
73
- expect(target.url, 'http://127.0.0.1:38900/modules/webview/demo.html');
74
- expect(target.asset, isNull);
75
- });
76
-
77
- test('asset:// is a Flutter asset without one', () {
78
- final target = fjsResolveWebViewSrc('asset://demo.html');
79
- // loadFlutterAsset takes a key, not a URL — hence the separate shape
80
- expect(target.asset, 'assets/fjs/modules/webview/demo.html');
81
- expect(target.url, isNull);
82
- expect(target.suffix, isEmpty);
83
- });
84
-
85
- test('an asset key is separate from the document suffix', () {
86
- // FWFURLParsingError happens when `demo.html?q=hello` is used as the
87
- // manifest key. The suffix is restored on the local document URL
88
- // instead, so the page still receives its parameters.
89
- final release = fjsResolveWebViewSrc('asset://demo.html?q=hello#top');
90
- expect(release.asset, 'assets/fjs/modules/webview/demo.html');
91
- expect(release.suffix, '?q=hello#top');
92
-
93
- final dev = fjsResolveWebViewSrc(
94
- 'asset://demo.html?q=hello#top',
95
- devUri: Uri.parse('http://127.0.0.1:38900'),
96
- );
97
- expect(dev.url,
98
- 'http://127.0.0.1:38900/modules/webview/demo.html?q=hello#top');
99
- expect(dev.suffix, isEmpty);
100
- });
101
-
102
- test('a root path is the app\'s own page, not the module\'s', () {
103
- // classifySrc in ../../index.ts has the same three kinds; missing this
104
- // branch on one end shows up as `unsupported` + a warn on that end
105
- // only, never as an exception (constitution I).
106
- expect(fjsClassifyWebViewSrc('/html/guide.html'),
107
- FjsWebViewSrcKind.local);
108
- expect(fjsWebViewLocalPath('/html/guide.html'), 'html/guide.html');
109
- expect(fjsWebViewLocalPath('///html/guide.html'), 'html/guide.html');
110
- expect(fjsWebViewLocalPath('/../secret'), isNull);
111
- expect(fjsWebViewLocalPath('/'), isNull);
112
- });
113
-
114
- test('a root path goes to the dev server while fjs dev is connected', () {
115
- final target = fjsResolveWebViewSrc(
116
- '/html/guide.html',
117
- devUri: Uri.parse('http://127.0.0.1:38900/'),
118
- );
119
- expect(target.url, 'http://127.0.0.1:38900/html/guide.html');
120
- expect(target.asset, isNull);
121
- });
122
-
123
- test('a root path is an app asset without one', () {
124
- final target = fjsResolveWebViewSrc('/html/guide.html');
125
- expect(target.asset, 'assets/fjs/public/html/guide.html');
126
- expect(target.url, isNull);
127
- expect(target.suffix, isEmpty);
128
- });
129
-
130
- test('a root path splits key and suffix the same way asset:// does', () {
131
- final release = fjsResolveWebViewSrc('/html/guide.html?q=1#top');
132
- expect(release.asset, 'assets/fjs/public/html/guide.html');
133
- expect(release.suffix, '?q=1#top');
134
- });
135
-
136
- test('strips a fragment too', () {
137
- expect(fjsWebViewStripQuery('a.html#top'), 'a.html');
138
- expect(fjsWebViewStripQuery('a.html'), 'a.html');
139
- });
140
-
141
- test('http is left alone either way', () {
142
- expect(fjsResolveWebViewSrc('https://example.com/a').url,
143
- 'https://example.com/a');
144
- expect(
145
- fjsResolveWebViewSrc('https://example.com/a',
146
- devUri: Uri.parse('http://127.0.0.1:38900'))
147
- .url,
148
- 'https://example.com/a',
149
- );
150
- });
151
-
152
- test('nothing to load for empty and unsupported', () {
153
- expect(fjsResolveWebViewSrc('').isNothing, isTrue);
154
- expect(fjsResolveWebViewSrc('file:///x').isNothing, isTrue);
155
- expect(fjsResolveWebViewSrc('asset://../x').isNothing, isTrue);
156
- });
157
- });
158
-
159
- group('load cycle', () {
160
- test('reports one terminal event per load', () {
161
- final cycle = FjsWebViewLoadCycle();
162
- final generation = cycle.begin();
163
- expect(cycle.finish(generation), isTrue);
164
- // error after load, or a second onPageFinished, is not a second event
165
- expect(cycle.finish(generation), isFalse);
166
- });
167
-
168
- test('drops the previous page after the src changes', () {
169
- final cycle = FjsWebViewLoadCycle();
170
- final first = cycle.begin();
171
- final second = cycle.begin();
172
- expect(cycle.finish(first), isFalse);
173
- expect(cycle.finish(second), isTrue);
174
- });
175
-
176
- test('accepts messages only from the current page', () {
177
- final cycle = FjsWebViewLoadCycle();
178
- final first = cycle.begin();
179
- expect(cycle.accepts(first), isTrue);
180
- cycle.begin();
181
- expect(cycle.accepts(first), isFalse);
182
- });
183
- });
184
-
185
- group('release asset navigation', () {
186
- test('reattaches parameters before the first page script', () {
187
- final navigation = FjsWebViewAssetNavigation('?q=hello#top');
188
- expect(
189
- navigation.redirect('file:///bundle/demo.html'),
190
- 'file:///bundle/demo.html?q=hello#top',
191
- );
192
- expect(
193
- navigation.accepts('file:///bundle/demo.html?q=hello#top'),
194
- isTrue,
195
- );
196
- expect(navigation.redirect('file:///bundle/demo.html'), isNull);
197
- });
198
-
199
- test('does not redirect an already parameterized URL', () {
200
- final navigation = FjsWebViewAssetNavigation('?q=hello');
201
- expect(
202
- navigation.redirect('file:///bundle/demo.html?q=hello'),
203
- isNull,
204
- );
205
- expect(navigation.redirect('file:///bundle/demo.html'), isNotNull);
206
- });
207
-
208
- test('stops protecting the base URL after the redirected page finishes',
209
- () {
210
- final navigation = FjsWebViewAssetNavigation('?q=hello');
211
- navigation.redirect('file:///bundle/demo.html');
212
- expect(
213
- navigation.shouldPreventBaseNavigation('file:///bundle/demo.html'),
214
- isTrue,
215
- );
216
- navigation.markFinished('file:///bundle/demo.html?q=hello');
217
- expect(
218
- navigation.shouldPreventBaseNavigation('file:///bundle/demo.html'),
219
- isFalse,
220
- );
221
- });
222
- });
223
-
224
- group('box', () {
225
- test('needs a bounded height, because a page has no natural one', () {
226
- expect(
227
- fjsWebViewFitsBox(
228
- const BoxConstraints.tightFor(width: 300, height: 200)),
229
- isTrue,
230
- );
231
- expect(
232
- fjsWebViewFitsBox(const BoxConstraints(maxWidth: 300)),
233
- isFalse,
234
- );
235
- });
236
- });
237
-
238
- group('widget', () {
239
- testWidgets('an empty src builds nothing at all', (tester) async {
240
- final events = <(int, String?)>[];
241
- await tester.pumpWidget(MaterialApp(
242
- home: FjsWebViewWidget(
243
- node: nodeWith(const {}),
244
- dispatch: (id, type, {String? text}) => events.add((type, text)),
245
- ),
246
- ));
247
- expect(find.byType(SizedBox), findsOneWidget);
248
- expect(events, isEmpty);
249
- });
250
-
251
- testWidgets('an unsupported scheme warns once and loads nothing',
252
- (tester) async {
253
- final logs = <String>[];
254
- final original = debugPrint;
255
- debugPrint = (message, {wrapWidth}) => logs.add(message ?? '');
256
- try {
257
- await tester.pumpWidget(MaterialApp(
258
- home: FjsWebViewWidget(
259
- node: nodeWith(const {'src': 'file:///etc/passwd'}),
260
- dispatch: (id, type, {String? text}) {},
261
- ),
262
- ));
263
- } finally {
264
- debugPrint = original;
265
- }
266
- expect(logs.where((l) => l.contains('file:///etc/passwd')), isNotEmpty);
267
- });
268
-
269
- testWidgets('claims gestures inside the platform view', (tester) async {
270
- final platform = _FakeWebViewPlatform();
271
- WebViewPlatform.instance = platform;
272
- addTearDown(() => WebViewPlatform.instance = _FakeWebViewPlatform());
273
-
274
- await tester.pumpWidget(MaterialApp(
275
- home: FjsWebViewWidget(
276
- node: nodeWith(const {'src': 'https://example.com'}),
277
- dispatch: (id, type, {String? text}) {},
278
- ),
279
- ));
280
-
281
- final params = platform.lastWidgetParams;
282
- expect(params, isNotNull);
283
- expect(params!.gestureRecognizers, hasLength(1));
284
- expect(
285
- params.gestureRecognizers.single.constructor(),
286
- isA<EagerGestureRecognizer>(),
287
- );
288
- });
289
- });
290
- }
291
-
292
- class _FakeWebViewPlatform extends WebViewPlatform {
293
- PlatformWebViewWidgetCreationParams? lastWidgetParams;
294
-
295
- @override
296
- PlatformWebViewController createPlatformWebViewController(
297
- PlatformWebViewControllerCreationParams params,
298
- ) => _FakeWebViewController(params);
299
-
300
- @override
301
- PlatformWebViewWidget createPlatformWebViewWidget(
302
- PlatformWebViewWidgetCreationParams params,
303
- ) {
304
- lastWidgetParams = params;
305
- return _FakeWebViewWidget(params);
306
- }
307
-
308
- @override
309
- PlatformNavigationDelegate createPlatformNavigationDelegate(
310
- PlatformNavigationDelegateCreationParams params,
311
- ) => _FakeNavigationDelegate(params);
312
- }
313
-
314
- class _FakeWebViewController extends PlatformWebViewController {
315
- _FakeWebViewController(super.params) : super.implementation();
316
-
317
- @override
318
- Future<void> setJavaScriptMode(JavaScriptMode javaScriptMode) async {}
319
-
320
- @override
321
- Future<void> setPlatformNavigationDelegate(
322
- PlatformNavigationDelegate handler,
323
- ) async {}
324
-
325
- @override
326
- Future<void> addJavaScriptChannel(
327
- JavaScriptChannelParams javaScriptChannelParams,
328
- ) async {}
329
-
330
- @override
331
- Future<void> loadFlutterAsset(String key) async {}
332
-
333
- @override
334
- Future<void> loadRequest(LoadRequestParams params) async {}
335
- }
336
-
337
- class _FakeWebViewWidget extends PlatformWebViewWidget {
338
- _FakeWebViewWidget(super.params) : super.implementation();
339
-
340
- @override
341
- Widget build(BuildContext context) => const SizedBox();
342
- }
343
-
344
- class _FakeNavigationDelegate extends PlatformNavigationDelegate {
345
- _FakeNavigationDelegate(super.params) : super.implementation();
346
-
347
- // PlatformNavigationDelegate's setters throw UnimplementedError by
348
- // default, and NavigationDelegate's constructor calls every one it was
349
- // given a callback for. The widget under test registers all four, so a
350
- // fake that overrides none cannot be built at all.
351
- @override
352
- Future<void> setOnNavigationRequest(
353
- NavigationRequestCallback onNavigationRequest,
354
- ) async {}
355
-
356
- @override
357
- Future<void> setOnPageFinished(PageEventCallback onPageFinished) async {}
358
-
359
- @override
360
- Future<void> setOnPageStarted(PageEventCallback onPageStarted) async {}
361
-
362
- @override
363
- Future<void> setOnWebResourceError(
364
- WebResourceErrorCallback onWebResourceError,
365
- ) async {}
366
-
367
- @override
368
- Future<void> setOnHttpError(HttpResponseErrorCallback onHttpError) async {}
369
- }