Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions lib/screens/common_widgets/env_trigger_json_editing.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import 'package:apidash/widgets/editor_json.dart';
import 'package:flutter/material.dart';
import 'package:json_field_editor/json_field_editor.dart';
import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart';
import 'env_trigger_options.dart';

class EnvironmentTriggerJsonEditor extends StatefulWidget {
const EnvironmentTriggerJsonEditor({
super.key,
required this.keyId,
this.initialValue,
this.controller,
this.focusNode,
this.onChanged,
this.onTextSubmitted,
this.style,
this.decoration,
this.optionsWidthFactor,
this.autocompleteNoTrigger,
this.readOnly = false,
this.obscureText = false,
}) : assert(
!(controller != null && initialValue != null),
'controller and initialValue cannot be simultaneously defined.',
);

final String keyId;
final String? initialValue;
final TextEditingController? controller;
final FocusNode? focusNode;
final void Function(String)? onChanged;
final void Function(String)? onTextSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
final double? optionsWidthFactor;
final AutocompleteNoTrigger? autocompleteNoTrigger;
final bool readOnly;
final bool obscureText;

@override
State<EnvironmentTriggerJsonEditor> createState() =>
EnvironmentTriggerJsonEditorState();
}

class EnvironmentTriggerJsonEditorState
extends State<EnvironmentTriggerJsonEditor> {
late JsonTextFieldController controller;
late FocusNode _focusNode;

@override
void initState() {
super.initState();
if (widget.controller != null) {
controller = widget.controller as JsonTextFieldController;
} else {
controller = JsonTextFieldController();
}

_focusNode = widget.focusNode ??
FocusNode(debugLabel: "env Trigger Editor Focus Node");
}

@override
void dispose() {
controller.dispose();
super.dispose();
}

@override
void didUpdateWidget(EnvironmentTriggerJsonEditor oldWidget) {
super.didUpdateWidget(oldWidget);
if ((oldWidget.keyId != widget.keyId) ||
(oldWidget.initialValue != widget.initialValue)) {
if (widget.controller != null) {
controller = widget.controller as JsonTextFieldController;
} else {
controller = JsonTextFieldController();
}
controller.text = widget.initialValue ?? '';
}
}

@override
Widget build(BuildContext context) {
return MultiTriggerAutocomplete(
key: Key(widget.keyId),
textEditingController: controller,
focusNode: _focusNode,
optionsWidthFactor: widget.optionsWidthFactor ?? 1,
optionsAlignment: OptionsAlignment.topStart,
autocompleteTriggers: [
if (widget.autocompleteNoTrigger != null) widget.autocompleteNoTrigger!,
AutocompleteTrigger(
trigger: '${controller.text}{',
triggerEnd: "}}",
triggerOnlyAfterSpace: false,
optionsViewBuilder: (context, autocompleteQuery, controller) {
return EnvironmentTriggerOptions(
query: autocompleteQuery.query,
onSuggestionTap: (suggestion) {
final autocomplete = MultiTriggerAutocomplete.of(context);
autocomplete.acceptAutocompleteOption(
'{${suggestion.variable.key}',
);
widget.onChanged?.call(controller.text);
},
);
},
),
AutocompleteTrigger(
trigger: '${controller.text}{{',
triggerEnd: "}}",
triggerOnlyAfterSpace: true,
optionsViewBuilder: (context, autocompleteQuery, controller) {
return EnvironmentTriggerOptions(
query: autocompleteQuery.query,
onSuggestionTap: (suggestion) {
final autocomplete = MultiTriggerAutocomplete.of(context);
autocomplete.acceptAutocompleteOption(
'{${suggestion.variable.key}',
);
widget.onChanged?.call(controller.text);
},
);
},
),
],
fieldViewBuilder: (context, textEditingController, focusNode) {
return JsonTextFieldEditor(
key: Key("${widget.keyId}-json-body"),
fieldKey: "${widget.keyId}-json-body-editor",
isDark: Theme.of(context).brightness == Brightness.dark,
initialValue: widget.initialValue,
onChanged: (String value) {
widget.onChanged?.call(value);
},
readOnly: widget.readOnly,
jsonTextFieldController:
textEditingController as JsonTextFieldController,
focusNode: focusNode,
);
},
);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:apidash/screens/common_widgets/env_trigger_json_editing.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import 'package:json_field_editor/json_field_editor.dart';
import 'request_form_data.dart';

class EditRequestBody extends ConsumerWidget {
Expand Down Expand Up @@ -47,17 +49,14 @@ class EditRequestBody extends ConsumerWidget {
const Padding(padding: kPh4, child: FormDataWidget()),
ContentType.json => Padding(
padding: kPt5o10,
child: JsonTextFieldEditor(
key: Key("$selectedId-json-body"),
fieldKey: "$selectedId-json-body-editor-$darkMode",
isDark: darkMode,
child: EnvironmentTriggerJsonEditor(
keyId: "$selectedId-environment-trigger",
initialValue: requestModel?.httpRequestModel?.body,
onChanged: (String value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(body: value);
},
hintText: kHintJson,
),
),
_ => Padding(
Expand Down
10 changes: 8 additions & 2 deletions lib/widgets/editor_json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ class JsonTextFieldEditor extends StatefulWidget {
this.hintText,
this.readOnly = false,
this.isDark = false,
this.jsonTextFieldController,
this.focusNode,
});

final String fieldKey;
final FocusNode? focusNode;
final Function(String)? onChanged;
final String? initialValue;
final String? hintText;
final JsonTextFieldController ? jsonTextFieldController;
final bool readOnly;
final bool isDark;

Expand All @@ -28,7 +32,7 @@ class JsonTextFieldEditor extends StatefulWidget {
}

class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
final JsonTextFieldController controller = JsonTextFieldController();
late JsonTextFieldController controller;
late final FocusNode editorFocusNode;

void insertTab() {
Expand All @@ -51,6 +55,8 @@ class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
@override
void initState() {
super.initState();
controller =
widget.jsonTextFieldController ?? JsonTextFieldController();
if (widget.initialValue != null) {
controller.text = widget.initialValue!;
}
Expand All @@ -60,7 +66,7 @@ class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
// controller.formatJson(sortJson: false);
// setState(() {});
// });
editorFocusNode = FocusNode(debugLabel: "Editor Focus Node");
editorFocusNode = widget.focusNode ?? FocusNode(debugLabel: "Editor Focus Node");
}

@override
Expand Down
117 changes: 117 additions & 0 deletions test/screens/common_widgets/env_trigger_json_editing_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import 'package:apidash/widgets/editor_json.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/screens/common_widgets/env_trigger_json_editing.dart';

main() {
const envMap = {
kGlobalEnvironmentId: [
EnvironmentVariableModel(key: 'key1', value: 'value1'),
EnvironmentVariableModel(key: 'key2', value: 'value2'),
],
'activeEnvId': [
EnvironmentVariableModel(key: 'key2', value: 'value1'),
EnvironmentVariableModel(key: 'key3', value: 'value2'),
],
};

const suggestions = [
EnvironmentVariableSuggestion(
environmentId: 'activeEnvId',
variable: EnvironmentVariableModel(key: 'key2', value: 'value1'),
),
EnvironmentVariableSuggestion(
environmentId: 'activeEnvId',
variable: EnvironmentVariableModel(key: 'key3', value: 'value2'),
),
EnvironmentVariableSuggestion(
environmentId: kGlobalEnvironmentId,
variable: EnvironmentVariableModel(key: 'key1', value: 'value1'),
),
EnvironmentVariableSuggestion(
environmentId: kGlobalEnvironmentId,
variable: EnvironmentVariableModel(key: 'key2', value: 'value2'),
),
];

testWidgets('EnvironmentTriggerJsonEditor updates controller text',
(WidgetTester tester) async {
final fieldKey = GlobalKey<EnvironmentTriggerJsonEditorState>();
const initialValue = 'initial';
const updatedValue = 'updated';

await tester.pumpWidget(
Portal(
child: MaterialApp(
home: Scaffold(
body: EnvironmentTriggerJsonEditor(
key: fieldKey,
keyId: 'testKey',
initialValue: initialValue,
),
),
),
),
);

Finder field = find.byType(JsonTextFieldEditor);
expect(field, findsOneWidget);
expect(fieldKey.currentState!.controller.text, initialValue);

await tester.pumpWidget(
Portal(
child: MaterialApp(
home: Scaffold(
body: EnvironmentTriggerJsonEditor(
key: fieldKey,
keyId: 'testKey',
initialValue: updatedValue,
),
),
),
),
);

expect(fieldKey.currentState!.controller.text, updatedValue);
});

testWidgets(
'EnvironmentTriggerJsonEditor shows suggestions when trigger typed',
(WidgetTester tester) async {
final fieldKey = GlobalKey<EnvironmentTriggerJsonEditorState>();
const textWithSuggestionTrigger = '{"Test" : {{';

await tester.pumpWidget(
ProviderScope(
overrides: [
availableEnvironmentVariablesStateProvider
.overrideWith((ref) => envMap),
activeEnvironmentIdStateProvider.overrideWith((ref) => 'activeEnvId'),
],
child: Portal(
child: MaterialApp(
home: Scaffold(
body: EnvironmentTriggerJsonEditor(
key: fieldKey,
keyId: 'testKey',
initialValue: textWithSuggestionTrigger,
),
),
),
),
),
);

await tester.tap(find.byType(JsonTextFieldEditor));
await tester.pumpAndSettle();

expect(find.byType(ClipRRect), findsOneWidget);
expect(find.byType(ListView), findsOneWidget);
expect(find.byType(ListTile), findsNWidgets(3));
});
}