mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: cannot click on links (#3017)
This commit is contained in:
@ -8,7 +8,6 @@ import 'package:integration_test/integration_test.dart';
|
|||||||
|
|
||||||
import 'util/database_test_op.dart';
|
import 'util/database_test_op.dart';
|
||||||
import 'util/emoji.dart';
|
import 'util/emoji.dart';
|
||||||
import 'util/ime.dart';
|
|
||||||
import 'util/util.dart';
|
import 'util/util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -9,6 +9,7 @@ import 'document_with_inline_math_equation_test.dart'
|
|||||||
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||||
import 'edit_document_test.dart' as document_edit_test;
|
import 'edit_document_test.dart' as document_edit_test;
|
||||||
|
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||||
|
|
||||||
void startTesting() {
|
void startTesting() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -20,5 +21,6 @@ void startTesting() {
|
|||||||
document_with_inline_page_test.main();
|
document_with_inline_page_test.main();
|
||||||
document_with_inline_math_equation_test.main();
|
document_with_inline_math_equation_test.main();
|
||||||
document_with_cover_image_test.main();
|
document_with_cover_image_test.main();
|
||||||
|
document_with_outline_block.main();
|
||||||
document_with_toggle_list_test.main();
|
document_with_toggle_list_test.main();
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import '../util/ime.dart';
|
|
||||||
import '../util/util.dart';
|
import '../util/util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
|
||||||
|
|
||||||
|
import '../util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('test editing link in document', () {
|
||||||
|
late MockUrlLauncher mock;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mock = MockUrlLauncher();
|
||||||
|
UrlLauncherPlatform.instance = mock;
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('insert/edit/open link', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new document
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the first line of the document
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
// insert a inline page
|
||||||
|
const link = 'AppFlowy';
|
||||||
|
await tester.ime.insertText(link);
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.single(path: [0], startOffset: 0, endOffset: link.length),
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the link button
|
||||||
|
final linkButton = find.byTooltip(
|
||||||
|
'Link',
|
||||||
|
);
|
||||||
|
await tester.tapButton(linkButton);
|
||||||
|
expect(find.text('Add your link', findRichText: true), findsOneWidget);
|
||||||
|
|
||||||
|
// input the link
|
||||||
|
const url = 'https://appflowy.io';
|
||||||
|
final textField = find.byWidgetPredicate(
|
||||||
|
(widget) => widget is TextField && widget.decoration!.hintText == 'URL',
|
||||||
|
);
|
||||||
|
await tester.enterText(textField, url);
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// single-click the link menu to show the menu
|
||||||
|
await tester.tapButton(find.text(link, findRichText: true));
|
||||||
|
expect(find.text('Open link', findRichText: true), findsOneWidget);
|
||||||
|
expect(find.text('Copy link', findRichText: true), findsOneWidget);
|
||||||
|
expect(find.text('Remove link', findRichText: true), findsOneWidget);
|
||||||
|
|
||||||
|
// double-click the link menu to open the link
|
||||||
|
mock
|
||||||
|
..setLaunchExpectations(
|
||||||
|
url: url,
|
||||||
|
useSafariVC: false,
|
||||||
|
useWebView: false,
|
||||||
|
universalLinksOnly: false,
|
||||||
|
enableJavaScript: true,
|
||||||
|
enableDomStorage: true,
|
||||||
|
headers: <String, String>{},
|
||||||
|
webOnlyWindowName: null,
|
||||||
|
launchMode: PreferredLaunchMode.platformDefault,
|
||||||
|
)
|
||||||
|
..setResponse(true);
|
||||||
|
|
||||||
|
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||||
|
await tester.doubleTapAt(
|
||||||
|
tester.getTopLeft(find.text(link, findRichText: true)).translate(5, 5),
|
||||||
|
);
|
||||||
|
expect(mock.canLaunchCalled, isTrue);
|
||||||
|
expect(mock.launchCalled, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import '../util/ime.dart';
|
|
||||||
import '../util/util.dart';
|
import '../util/util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import '../util/ime.dart';
|
|
||||||
import '../util/util.dart';
|
import '../util/util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import '../util/ime.dart';
|
|
||||||
import '../util/util.dart';
|
import '../util/util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -12,7 +12,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'ime.dart';
|
|
||||||
import 'util.dart';
|
import 'util.dart';
|
||||||
|
|
||||||
extension EditorWidgetTester on WidgetTester {
|
extension EditorWidgetTester on WidgetTester {
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||||
|
import 'package:url_launcher_platform_interface/link.dart';
|
||||||
|
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
|
||||||
|
|
||||||
|
class MockUrlLauncher extends Fake
|
||||||
|
with MockPlatformInterfaceMixin
|
||||||
|
implements UrlLauncherPlatform {
|
||||||
|
String? url;
|
||||||
|
PreferredLaunchMode? launchMode;
|
||||||
|
bool? useSafariVC;
|
||||||
|
bool? useWebView;
|
||||||
|
bool? enableJavaScript;
|
||||||
|
bool? enableDomStorage;
|
||||||
|
bool? universalLinksOnly;
|
||||||
|
Map<String, String>? headers;
|
||||||
|
String? webOnlyWindowName;
|
||||||
|
|
||||||
|
bool? response;
|
||||||
|
|
||||||
|
bool closeWebViewCalled = false;
|
||||||
|
bool canLaunchCalled = false;
|
||||||
|
bool launchCalled = false;
|
||||||
|
|
||||||
|
// ignore: use_setters_to_change_properties
|
||||||
|
void setCanLaunchExpectations(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLaunchExpectations({
|
||||||
|
required String url,
|
||||||
|
PreferredLaunchMode? launchMode,
|
||||||
|
bool? useSafariVC,
|
||||||
|
bool? useWebView,
|
||||||
|
required bool enableJavaScript,
|
||||||
|
required bool enableDomStorage,
|
||||||
|
required bool universalLinksOnly,
|
||||||
|
required Map<String, String> headers,
|
||||||
|
required String? webOnlyWindowName,
|
||||||
|
}) {
|
||||||
|
this.url = url;
|
||||||
|
this.launchMode = launchMode;
|
||||||
|
this.useSafariVC = useSafariVC;
|
||||||
|
this.useWebView = useWebView;
|
||||||
|
this.enableJavaScript = enableJavaScript;
|
||||||
|
this.enableDomStorage = enableDomStorage;
|
||||||
|
this.universalLinksOnly = universalLinksOnly;
|
||||||
|
this.headers = headers;
|
||||||
|
this.webOnlyWindowName = webOnlyWindowName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: use_setters_to_change_properties
|
||||||
|
void setResponse(bool response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
LinkDelegate? get linkDelegate => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canLaunch(String url) async {
|
||||||
|
expect(url, this.url);
|
||||||
|
canLaunchCalled = true;
|
||||||
|
return response!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> launch(
|
||||||
|
String url, {
|
||||||
|
required bool useSafariVC,
|
||||||
|
required bool useWebView,
|
||||||
|
required bool enableJavaScript,
|
||||||
|
required bool enableDomStorage,
|
||||||
|
required bool universalLinksOnly,
|
||||||
|
required Map<String, String> headers,
|
||||||
|
String? webOnlyWindowName,
|
||||||
|
}) async {
|
||||||
|
expect(url, this.url);
|
||||||
|
expect(useSafariVC, this.useSafariVC);
|
||||||
|
expect(useWebView, this.useWebView);
|
||||||
|
expect(enableJavaScript, this.enableJavaScript);
|
||||||
|
expect(enableDomStorage, this.enableDomStorage);
|
||||||
|
expect(universalLinksOnly, this.universalLinksOnly);
|
||||||
|
expect(headers, this.headers);
|
||||||
|
expect(webOnlyWindowName, this.webOnlyWindowName);
|
||||||
|
launchCalled = true;
|
||||||
|
return response!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> launchUrl(String url, LaunchOptions options) async {
|
||||||
|
expect(url, this.url);
|
||||||
|
expect(options.mode, launchMode);
|
||||||
|
expect(options.webViewConfiguration.enableJavaScript, enableJavaScript);
|
||||||
|
expect(options.webViewConfiguration.enableDomStorage, enableDomStorage);
|
||||||
|
expect(options.webViewConfiguration.headers, headers);
|
||||||
|
expect(options.webOnlyWindowName, webOnlyWindowName);
|
||||||
|
launchCalled = true;
|
||||||
|
return response!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> closeWebView() async {
|
||||||
|
closeWebViewCalled = true;
|
||||||
|
}
|
||||||
|
}
|
@ -4,3 +4,5 @@ export 'settings.dart';
|
|||||||
export 'data.dart';
|
export 'data.dart';
|
||||||
export 'expectation.dart';
|
export 'expectation.dart';
|
||||||
export 'editor_test_operations.dart';
|
export 'editor_test_operations.dart';
|
||||||
|
export 'mock/mock_url_launcher.dart';
|
||||||
|
export 'ime.dart';
|
||||||
|
@ -357,6 +357,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
outlineItem,
|
outlineItem,
|
||||||
mathEquationItem,
|
mathEquationItem,
|
||||||
codeBlockItem,
|
codeBlockItem,
|
||||||
|
toggleListBlockItem,
|
||||||
emojiMenuItem,
|
emojiMenuItem,
|
||||||
autoGeneratorMenuItem,
|
autoGeneratorMenuItem,
|
||||||
];
|
];
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -42,6 +44,15 @@ Node toggleListBlockNode({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defining the toggle list block menu item
|
||||||
|
SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node(
|
||||||
|
name: LocaleKeys.document_plugins_toggleList.tr(),
|
||||||
|
iconData: Icons.arrow_right,
|
||||||
|
keywords: ['collapsed list', 'toggle list', 'list'],
|
||||||
|
nodeBuilder: (editorState) => toggleListBlockNode(),
|
||||||
|
replace: (_, node) => node.delta?.isEmpty ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
|
class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
|
||||||
ToggleListBlockComponentBuilder({
|
ToggleListBlockComponentBuilder({
|
||||||
this.configuration = const BlockComponentConfiguration(),
|
this.configuration = const BlockComponentConfiguration(),
|
||||||
|
@ -220,6 +220,12 @@ class EditorStyleCustomizer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return textSpan;
|
return defaultTextSpanDecoratorForAttribute(
|
||||||
|
context,
|
||||||
|
node,
|
||||||
|
index,
|
||||||
|
text,
|
||||||
|
textSpan,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "33b18d9"
|
ref: "023f3c8"
|
||||||
resolved-ref: "33b18d98dcc6db996eef3d6b869f293da3da3615"
|
resolved-ref: "023f3c835dc427a932bb2022a0d213c0084ffb99"
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
@ -1002,7 +1002,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
||||||
@ -1567,7 +1567,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.5"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
||||||
|
@ -115,6 +115,9 @@ dev_dependencies:
|
|||||||
json_serializable: ^6.7.0
|
json_serializable: ^6.7.0
|
||||||
envied_generator: ^0.3.0+3
|
envied_generator: ^0.3.0+3
|
||||||
|
|
||||||
|
plugin_platform_interface: any
|
||||||
|
url_launcher_platform_interface: any
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
http: ^1.0.0
|
http: ^1.0.0
|
||||||
|
|
||||||
|
@ -453,6 +453,7 @@
|
|||||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||||
"discardResponse": "Do you want to discard the AI responses?",
|
"discardResponse": "Do you want to discard the AI responses?",
|
||||||
"createInlineMathEquation": "Create equation",
|
"createInlineMathEquation": "Create equation",
|
||||||
|
"toggleList": "Toggle List",
|
||||||
"cover": {
|
"cover": {
|
||||||
"changeCover": "Change Cover",
|
"changeCover": "Change Cover",
|
||||||
"colors": "Colors",
|
"colors": "Colors",
|
||||||
|
Reference in New Issue
Block a user