mirror of
https://github.com/foss42/apidash.git
synced 2025-06-29 12:26:41 +08:00
Merge branch 'foss42:main' into resolve-issue-136
This commit is contained in:
@ -56,6 +56,7 @@ const kForegroundOpacity = 0.05;
|
||||
const kOverlayBackgroundOpacity = 0.5;
|
||||
|
||||
const kTextStyleButton = TextStyle(fontWeight: FontWeight.bold);
|
||||
const kTextStyleTab = TextStyle(fontSize: 14);
|
||||
const kTextStyleButtonSmall = TextStyle(fontSize: 12);
|
||||
const kFormDataButtonLabelTextStyle = TextStyle(
|
||||
fontSize: 12,
|
||||
@ -71,9 +72,13 @@ const kP5 = EdgeInsets.all(5);
|
||||
const kP8 = EdgeInsets.all(8);
|
||||
const kPs8 = EdgeInsets.only(left: 8);
|
||||
const kPs2 = EdgeInsets.only(left: 2);
|
||||
const kPe8 = EdgeInsets.only(right: 8.0);
|
||||
const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5);
|
||||
const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10);
|
||||
const kP10 = EdgeInsets.all(10);
|
||||
const kPv8 = EdgeInsets.symmetric(vertical: 8);
|
||||
const kPv2 = EdgeInsets.symmetric(vertical: 2);
|
||||
const kPh2 = EdgeInsets.symmetric(horizontal: 2);
|
||||
const kPt24o8 = EdgeInsets.only(top: 24, left: 8.0, right: 8.0, bottom: 8.0);
|
||||
const kPt5o10 =
|
||||
EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 10.0);
|
||||
@ -102,8 +107,9 @@ const kP8CollectionPane = EdgeInsets.only(
|
||||
const kPb10 = EdgeInsets.only(
|
||||
bottom: 10,
|
||||
);
|
||||
const kPr8CollectionPane = EdgeInsets.only(right: 8.0);
|
||||
const kpsV5 = EdgeInsets.symmetric(vertical: 2);
|
||||
const kPb15 = EdgeInsets.only(
|
||||
bottom: 15,
|
||||
);
|
||||
const kHSpacer4 = SizedBox(width: 4);
|
||||
const kHSpacer5 = SizedBox(width: 5);
|
||||
const kHSpacer10 = SizedBox(width: 10);
|
||||
@ -112,9 +118,10 @@ const kVSpacer5 = SizedBox(height: 5);
|
||||
const kVSpacer8 = SizedBox(height: 8);
|
||||
const kVSpacer10 = SizedBox(height: 10);
|
||||
const kVSpacer20 = SizedBox(height: 20);
|
||||
const kVSpacer40 = SizedBox(height: 40);
|
||||
|
||||
const kTabAnimationDuration = Duration(milliseconds: 200);
|
||||
const kTabHeight = 45.0;
|
||||
const kTabHeight = 32.0;
|
||||
const kHeaderHeight = 32.0;
|
||||
const kSegmentHeight = 24.0;
|
||||
const kTextButtonMinWidth = 44.0;
|
||||
@ -533,3 +540,36 @@ const kLabelSave = "Save";
|
||||
const kLabelDownload = "Download";
|
||||
const kLabelSaving = "Saving";
|
||||
const kLabelSaved = "Saved";
|
||||
// Request Pane
|
||||
const kLabelRequest = "Request";
|
||||
const kLabelHideCode = "Hide Code";
|
||||
const kLabelViewCode = "View Code";
|
||||
const kLabelURLParams = "URL Params";
|
||||
const kLabelHeaders = "Headers";
|
||||
const kLabelBody = "Body";
|
||||
const kNameCheckbox = "Checkbox";
|
||||
const kNameURLParam = "URL Parameter";
|
||||
const kNameHeader = "Header Name";
|
||||
const kNameValue = "Value";
|
||||
const kNameField = "Field";
|
||||
const kHintAddURLParam = "Add URL Parameter";
|
||||
const kHintAddValue = "Add Value";
|
||||
const kHintAddName = "Add Name";
|
||||
const kHintAddFieldName = "Add Field Name";
|
||||
const kLabelAddParam = "Add Param";
|
||||
const kLabelAddHeader = "Add Header";
|
||||
const kLabelSelectFile = "Select File";
|
||||
const kLabelAddFormField = "Add Form Field";
|
||||
// Response Pane
|
||||
const kLabelNotSent = "Not Sent";
|
||||
const kLabelResponse = "Response";
|
||||
const kLabelResponseBody = "Response Body";
|
||||
const kTooltipClearResponse = "Clear Response";
|
||||
const kHeaderRow = ["Header Name", "Header Value"];
|
||||
const kLabelRequestHeaders = "Request Headers";
|
||||
const kLabelResponseHeaders = "Response Headers";
|
||||
const kLabelItems = "items";
|
||||
const kMsgError = "Error: Response data does not exist.";
|
||||
const kMsgNullBody = "Response body is missing (null).";
|
||||
const kMsgNoContent = "No content";
|
||||
const kMsgUnknowContentType = "Unknown Response Content-Type";
|
||||
|
@ -95,6 +95,7 @@ class RequestModel {
|
||||
RequestModel duplicate({
|
||||
required String id,
|
||||
String? name,
|
||||
int? requestTabIndex,
|
||||
}) {
|
||||
return RequestModel(
|
||||
id: id,
|
||||
@ -102,6 +103,7 @@ class RequestModel {
|
||||
url: url,
|
||||
name: name ?? "${this.name} (copy)",
|
||||
description: description,
|
||||
requestTabIndex: requestTabIndex ?? 0,
|
||||
requestHeaders: requestHeaders != null ? [...requestHeaders!] : null,
|
||||
requestParams: requestParams != null ? [...requestParams!] : null,
|
||||
isHeaderEnabledList:
|
||||
|
@ -102,6 +102,7 @@ class CollectionStateNotifier
|
||||
final newModel = currentModel.duplicate(
|
||||
id: id,
|
||||
name: currentModel.name,
|
||||
requestTabIndex: currentModel.requestTabIndex,
|
||||
);
|
||||
var map = {...state!};
|
||||
map[id] = newModel;
|
||||
|
@ -23,3 +23,5 @@ final nameTextFieldFocusNodeProvider =
|
||||
});
|
||||
return focusNode;
|
||||
});
|
||||
|
||||
final searchQueryProvider = StateProvider<String>((ref) => '');
|
||||
|
@ -26,7 +26,7 @@ class CollectionPane extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: kPr8CollectionPane,
|
||||
padding: kPe8,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -69,7 +69,39 @@ class CollectionPane extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
kVSpacer8,
|
||||
kVSpacer10,
|
||||
Container(
|
||||
height: 30,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
kHSpacer5,
|
||||
Icon(
|
||||
Icons.filter_alt,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
kHSpacer5,
|
||||
Expanded(
|
||||
child: RawTextField(
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
hintText: "Filter by name or URL",
|
||||
onChanged: (value) {
|
||||
ref.read(searchQueryProvider.notifier).state =
|
||||
value.toLowerCase();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
kVSpacer10,
|
||||
const Expanded(
|
||||
child: RequestList(),
|
||||
),
|
||||
@ -109,41 +141,61 @@ class _RequestListState extends ConsumerState<RequestList> {
|
||||
final requestItems = ref.watch(collectionStateNotifierProvider)!;
|
||||
final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider
|
||||
.select((value) => value.alwaysShowCollectionPaneScrollbar));
|
||||
final filterQuery = ref.watch(searchQueryProvider).trim();
|
||||
|
||||
return Scrollbar(
|
||||
controller: controller,
|
||||
thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null,
|
||||
radius: const Radius.circular(12),
|
||||
child: ReorderableListView.builder(
|
||||
padding: kPr8CollectionPane,
|
||||
scrollController: controller,
|
||||
buildDefaultDragHandles: false,
|
||||
itemCount: requestSequence.length,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
if (oldIndex != newIndex) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.reorder(oldIndex, newIndex);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
var id = requestSequence[index];
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey(id),
|
||||
index: index,
|
||||
child: Padding(
|
||||
padding: kP1,
|
||||
child: RequestItem(
|
||||
id: id,
|
||||
requestModel: requestItems[id]!,
|
||||
),
|
||||
child: filterQuery.isEmpty
|
||||
? ReorderableListView.builder(
|
||||
padding: kPe8,
|
||||
scrollController: controller,
|
||||
buildDefaultDragHandles: false,
|
||||
itemCount: requestSequence.length,
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
if (oldIndex != newIndex) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.reorder(oldIndex, newIndex);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
var id = requestSequence[index];
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey(id),
|
||||
index: index,
|
||||
child: Padding(
|
||||
padding: kP1,
|
||||
child: RequestItem(
|
||||
id: id,
|
||||
requestModel: requestItems[id]!,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: ListView(
|
||||
padding: kPe8,
|
||||
controller: controller,
|
||||
children: requestSequence.map((id) {
|
||||
var item = requestItems[id]!;
|
||||
if (item.url.toLowerCase().contains(filterQuery) ||
|
||||
item.name.toLowerCase().contains(filterQuery)) {
|
||||
return Padding(
|
||||
padding: kP1,
|
||||
child: RequestItem(
|
||||
id: id,
|
||||
requestModel: item,
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
late int seed;
|
||||
final random = Random.secure();
|
||||
late List<FormDataModel> formRows;
|
||||
bool isAddingRow = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -45,13 +46,14 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
]
|
||||
: rF;
|
||||
formRows = isFormDataEmpty ? rows : rows + [kFormDataEmptyModel];
|
||||
isAddingRow = false;
|
||||
|
||||
DaviModel<FormDataModel> daviModelRows = DaviModel<FormDataModel>(
|
||||
rows: formRows,
|
||||
columns: [
|
||||
DaviColumn(
|
||||
cellPadding: kpsV5,
|
||||
name: 'Key',
|
||||
cellPadding: kPv2,
|
||||
name: kNameField,
|
||||
grow: 4,
|
||||
cellBuilder: (_, row) {
|
||||
int idx = row.index;
|
||||
@ -61,10 +63,13 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
child: FormDataField(
|
||||
keyId: "$selectedId-$idx-form-v-$seed",
|
||||
initialValue: formRows[idx].name,
|
||||
hintText: " Add Key",
|
||||
hintText: kHintAddFieldName,
|
||||
onChanged: (value) {
|
||||
formRows[idx] = formRows[idx].copyWith(name: value);
|
||||
if (isLast) formRows.add(kFormDataEmptyModel);
|
||||
if (isLast && !isAddingRow) {
|
||||
isAddingRow = true;
|
||||
formRows.add(kFormDataEmptyModel);
|
||||
}
|
||||
_onFieldChange(selectedId!);
|
||||
},
|
||||
colorScheme: Theme.of(context).colorScheme,
|
||||
@ -75,7 +80,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
type: value ?? FormDataType.text,
|
||||
);
|
||||
formRows[idx] = formRows[idx].copyWith(value: "");
|
||||
if (idx == formRows.length - 1 && hasChanged) {
|
||||
if (isLast && hasChanged) {
|
||||
formRows.add(kFormDataEmptyModel);
|
||||
}
|
||||
setState(() {});
|
||||
@ -88,7 +93,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
),
|
||||
DaviColumn(
|
||||
width: 40,
|
||||
cellPadding: kpsV5,
|
||||
cellPadding: kPv2,
|
||||
cellAlignment: Alignment.center,
|
||||
cellBuilder: (_, row) {
|
||||
return Text(
|
||||
@ -98,9 +103,9 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
},
|
||||
),
|
||||
DaviColumn(
|
||||
name: 'Value',
|
||||
name: kNameValue,
|
||||
grow: 4,
|
||||
cellPadding: kpsV5,
|
||||
cellPadding: kPv2,
|
||||
cellBuilder: (_, row) {
|
||||
int idx = row.index;
|
||||
bool isLast = idx + 1 == formRows.length;
|
||||
@ -140,7 +145,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
(formRows[idx].type == FormDataType.file &&
|
||||
formRows[idx].value.isNotEmpty)
|
||||
? formRows[idx].value.toString()
|
||||
: "Select File",
|
||||
: kLabelSelectFile,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: kFormDataButtonLabelTextStyle,
|
||||
@ -154,10 +159,13 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
: CellField(
|
||||
keyId: "$selectedId-$idx-form-v-$seed",
|
||||
initialValue: formRows[idx].value,
|
||||
hintText: " Add Value",
|
||||
hintText: kHintAddValue,
|
||||
onChanged: (value) {
|
||||
formRows[idx] = formRows[idx].copyWith(value: value);
|
||||
if (isLast) formRows.add(kFormDataEmptyModel);
|
||||
if (isLast && !isAddingRow) {
|
||||
isAddingRow = true;
|
||||
formRows.add(kFormDataEmptyModel);
|
||||
}
|
||||
_onFieldChange(selectedId!);
|
||||
},
|
||||
colorScheme: Theme.of(context).colorScheme,
|
||||
@ -208,13 +216,14 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
child: Davi<FormDataModel>(daviModelRows),
|
||||
),
|
||||
),
|
||||
kVSpacer20,
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
formRows.add(kFormDataEmptyModel);
|
||||
@ -222,7 +231,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text(
|
||||
"Add Form Data",
|
||||
kLabelAddFormField,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
|
@ -19,6 +19,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
final random = Random.secure();
|
||||
late List<NameValueModel> headerRows;
|
||||
late List<bool> isRowEnabledList;
|
||||
bool isAddingRow = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -52,12 +53,13 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
ref.read(selectedRequestModelProvider)?.isHeaderEnabledList ??
|
||||
List.filled(rH?.length ?? 0, true, growable: true);
|
||||
isRowEnabledList.add(false);
|
||||
isAddingRow = false;
|
||||
|
||||
DaviModel<NameValueModel> model = DaviModel<NameValueModel>(
|
||||
rows: headerRows,
|
||||
columns: [
|
||||
DaviColumn(
|
||||
name: 'Checkbox',
|
||||
name: kNameCheckbox,
|
||||
width: 30,
|
||||
cellBuilder: (_, row) {
|
||||
int idx = row.index;
|
||||
@ -78,7 +80,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
},
|
||||
),
|
||||
DaviColumn(
|
||||
name: 'Header Name',
|
||||
name: kNameHeader,
|
||||
width: 70,
|
||||
grow: 1,
|
||||
cellBuilder: (_, row) {
|
||||
@ -87,10 +89,11 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
return HeaderField(
|
||||
keyId: "$selectedId-$idx-headers-k-$seed",
|
||||
initialValue: headerRows[idx].name,
|
||||
hintText: "Add Header Name",
|
||||
hintText: kHintAddName,
|
||||
onChanged: (value) {
|
||||
headerRows[idx] = headerRows[idx].copyWith(name: value);
|
||||
if (isLast) {
|
||||
if (isLast && !isAddingRow) {
|
||||
isAddingRow = true;
|
||||
isRowEnabledList[idx] = true;
|
||||
headerRows.add(kNameValueEmptyModel);
|
||||
isRowEnabledList.add(false);
|
||||
@ -112,7 +115,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
},
|
||||
),
|
||||
DaviColumn(
|
||||
name: 'Header Value',
|
||||
name: kNameValue,
|
||||
grow: 1,
|
||||
cellBuilder: (_, row) {
|
||||
int idx = row.index;
|
||||
@ -120,10 +123,11 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
return CellField(
|
||||
keyId: "$selectedId-$idx-headers-v-$seed",
|
||||
initialValue: headerRows[idx].value,
|
||||
hintText: " Add Header Value",
|
||||
hintText: kHintAddValue,
|
||||
onChanged: (value) {
|
||||
headerRows[idx] = headerRows[idx].copyWith(value: value);
|
||||
if (isLast) {
|
||||
if (isLast && !isAddingRow) {
|
||||
isAddingRow = true;
|
||||
isRowEnabledList[idx] = true;
|
||||
headerRows.add(kNameValueEmptyModel);
|
||||
isRowEnabledList.add(false);
|
||||
@ -182,13 +186,14 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
child: Davi<NameValueModel>(model),
|
||||
),
|
||||
),
|
||||
kVSpacer40,
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
padding: kPb15,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
headerRows.add(kNameValueEmptyModel);
|
||||
@ -197,7 +202,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text(
|
||||
"Add Header",
|
||||
kLabelAddHeader,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
|
@ -20,6 +20,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
final random = Random.secure();
|
||||
late List<NameValueModel> paramRows;
|
||||
late List<bool> isRowEnabledList;
|
||||
bool isAddingRow = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -52,12 +53,13 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
ref.read(selectedRequestModelProvider)?.isParamEnabledList ??
|
||||
List.filled(rP?.length ?? 0, true, growable: true);
|
||||
isRowEnabledList.add(false);
|
||||
isAddingRow = false;
|
||||
|
||||
DaviModel<NameValueModel> model = DaviModel<NameValueModel>(
|
||||
rows: paramRows,
|
||||
columns: [
|
||||
DaviColumn(
|
||||
name: 'Checkbox',
|
||||
name: kNameCheckbox,
|
||||
width: 30,
|
||||
cellBuilder: (_, row) {
|
||||
int idx = row.index;
|
||||
@ -78,7 +80,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
},
|
||||
),
|
||||
DaviColumn(
|
||||
name: 'URL Parameter',
|
||||
name: kNameURLParam,
|
||||
width: 70,
|
||||
grow: 1,
|
||||
cellBuilder: (_, row) {
|
||||
@ -87,10 +89,11 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
return CellField(
|
||||
keyId: "$selectedId-$idx-params-k-$seed",
|
||||
initialValue: paramRows[idx].name,
|
||||
hintText: "Add URL Parameter",
|
||||
hintText: kHintAddURLParam,
|
||||
onChanged: (value) {
|
||||
paramRows[idx] = paramRows[idx].copyWith(name: value);
|
||||
if (isLast) {
|
||||
if (isLast && !isAddingRow) {
|
||||
isAddingRow = true;
|
||||
isRowEnabledList[idx] = true;
|
||||
paramRows.add(kNameValueEmptyModel);
|
||||
isRowEnabledList.add(false);
|
||||
@ -112,7 +115,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
},
|
||||
),
|
||||
DaviColumn(
|
||||
name: 'Value',
|
||||
name: kNameValue,
|
||||
grow: 1,
|
||||
cellBuilder: (_, row) {
|
||||
int idx = row.index;
|
||||
@ -120,10 +123,11 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
return CellField(
|
||||
keyId: "$selectedId-$idx-params-v-$seed",
|
||||
initialValue: paramRows[idx].value,
|
||||
hintText: "Add Value",
|
||||
hintText: kHintAddValue,
|
||||
onChanged: (value) {
|
||||
paramRows[idx] = paramRows[idx].copyWith(value: value);
|
||||
if (isLast) {
|
||||
if (isLast && !isAddingRow) {
|
||||
isAddingRow = true;
|
||||
isRowEnabledList[idx] = true;
|
||||
paramRows.add(kNameValueEmptyModel);
|
||||
isRowEnabledList.add(false);
|
||||
@ -182,13 +186,14 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
child: Davi<NameValueModel>(model),
|
||||
),
|
||||
),
|
||||
kVSpacer40,
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
padding: kPb15,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
paramRows.add(kNameValueEmptyModel);
|
||||
@ -197,7 +202,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text(
|
||||
"Add Param",
|
||||
kLabelAddParam,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
|
@ -34,7 +34,6 @@ class ResponseDetails extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var sm = ScaffoldMessenger.of(context);
|
||||
final responseStatus = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.responseStatus));
|
||||
final message = ref
|
||||
@ -52,8 +51,6 @@ class ResponseDetails extends ConsumerWidget {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.clearResponse(selectedRequest?.id);
|
||||
sm.hideCurrentSnackBar();
|
||||
sm.showSnackBar(getSnackBar('Response cleared'));
|
||||
},
|
||||
),
|
||||
const Expanded(
|
||||
|
@ -246,15 +246,12 @@ class ClearResponseButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tooltip(
|
||||
message: 'Clear response',
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(minimumSize: const Size(40, 40)),
|
||||
onPressed: onPressed,
|
||||
child: const Icon(
|
||||
Icons.delete,
|
||||
size: 20,
|
||||
),
|
||||
return IconButton(
|
||||
tooltip: kTooltipClearResponse,
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -157,6 +157,7 @@ class DropdownButtonCodegenLanguage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<CodegenLanguage>(
|
||||
isExpanded: true,
|
||||
focusColor: surfaceColor,
|
||||
value: codegenLanguage,
|
||||
icon: const Icon(
|
||||
@ -181,6 +182,8 @@ class DropdownButtonCodegenLanguage extends StatelessWidget {
|
||||
child: Text(
|
||||
value.label,
|
||||
style: kTextStyleButton,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -48,16 +48,12 @@ class _RequestPaneState extends State<RequestPane>
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: kPh20v10,
|
||||
padding: kP8,
|
||||
child: SizedBox(
|
||||
height: kHeaderHeight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"Request",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: widget.onPressedCodeButton,
|
||||
icon: Icon(
|
||||
@ -68,7 +64,8 @@ class _RequestPaneState extends State<RequestPane>
|
||||
label: SizedBox(
|
||||
width: 75,
|
||||
child: Text(
|
||||
widget.codePaneVisible ? "Hide Code" : "View Code"),
|
||||
widget.codePaneVisible ? kLabelHideCode : kLabelViewCode,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -79,18 +76,19 @@ class _RequestPaneState extends State<RequestPane>
|
||||
key: Key(widget.selectedId!),
|
||||
controller: _controller,
|
||||
overlayColor: kColorTransparentState,
|
||||
labelPadding: kPh2,
|
||||
onTap: widget.onTapTabBar,
|
||||
tabs: [
|
||||
TabLabel(
|
||||
text: 'URL Params',
|
||||
text: kLabelURLParams,
|
||||
showIndicator: widget.showIndicators[0],
|
||||
),
|
||||
TabLabel(
|
||||
text: 'Headers',
|
||||
text: kLabelHeaders,
|
||||
showIndicator: widget.showIndicators[1],
|
||||
),
|
||||
TabLabel(
|
||||
text: 'Body',
|
||||
text: kLabelBody,
|
||||
showIndicator: widget.showIndicators[2],
|
||||
),
|
||||
],
|
||||
|
@ -23,7 +23,7 @@ class NotSentWidget extends StatelessWidget {
|
||||
color: color,
|
||||
),
|
||||
Text(
|
||||
'Not Sent',
|
||||
kLabelNotSent,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(color: color),
|
||||
),
|
||||
@ -66,42 +66,19 @@ class ResponsePaneHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: kPh20v10,
|
||||
padding: kPv8,
|
||||
child: SizedBox(
|
||||
height: kHeaderHeight,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: "Response (",
|
||||
),
|
||||
TextSpan(
|
||||
text: "$responseStatus",
|
||||
style: TextStyle(
|
||||
color: getResponseStatusCodeColor(
|
||||
responseStatus,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
fontFamily: kCodeStyle.fontFamily,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ")",
|
||||
),
|
||||
],
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
kHSpacer20,
|
||||
kHSpacer10,
|
||||
Expanded(
|
||||
child: Text(
|
||||
message ?? "",
|
||||
"$responseStatus: ${message ?? '-'}",
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontFamily: kCodeStyle.fontFamily,
|
||||
color: getResponseStatusCodeColor(
|
||||
responseStatus,
|
||||
@ -110,10 +87,10 @@ class ResponsePaneHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
kHSpacer20,
|
||||
kHSpacer10,
|
||||
Text(
|
||||
humanizeDuration(time),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontFamily: kCodeStyle.fontFamily,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
@ -163,31 +140,15 @@ class _ResponseTabViewState extends State<ResponseTabView>
|
||||
TabBar(
|
||||
key: Key(widget.selectedId!),
|
||||
controller: _controller,
|
||||
labelPadding: kPh2,
|
||||
overlayColor: kColorTransparentState,
|
||||
onTap: (index) {},
|
||||
tabs: const [
|
||||
SizedBox(
|
||||
height: kTabHeight,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Body',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
TabLabel(
|
||||
text: kLabelResponseBody,
|
||||
),
|
||||
SizedBox(
|
||||
height: kTabHeight,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Headers',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
TabLabel(
|
||||
text: kLabelHeaders,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -227,8 +188,8 @@ class ResponseHeadersHeader extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"$name (${map.length} items)",
|
||||
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
||||
"$name (${map.length} $kLabelItems)",
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@ -243,8 +204,6 @@ class ResponseHeadersHeader extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
const kHeaderRow = ["Header Name", "Header Value"];
|
||||
|
||||
class ResponseHeaders extends StatelessWidget {
|
||||
const ResponseHeaders({
|
||||
super.key,
|
||||
@ -263,7 +222,7 @@ class ResponseHeaders extends StatelessWidget {
|
||||
children: [
|
||||
ResponseHeadersHeader(
|
||||
map: responseHeaders,
|
||||
name: "Response Headers",
|
||||
name: kLabelResponseHeaders,
|
||||
),
|
||||
if (responseHeaders.isNotEmpty) kVSpacer5,
|
||||
if (responseHeaders.isNotEmpty)
|
||||
@ -275,7 +234,7 @@ class ResponseHeaders extends StatelessWidget {
|
||||
kVSpacer10,
|
||||
ResponseHeadersHeader(
|
||||
map: requestHeaders,
|
||||
name: "Request Headers",
|
||||
name: kLabelRequestHeaders,
|
||||
),
|
||||
if (requestHeaders.isNotEmpty) kVSpacer5,
|
||||
if (requestHeaders.isNotEmpty)
|
||||
@ -302,20 +261,18 @@ class ResponseBody extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final responseModel = selectedRequestModel?.responseModel;
|
||||
if (responseModel == null) {
|
||||
return const ErrorMessage(
|
||||
message:
|
||||
'Error: Response data does not exist. $kUnexpectedRaiseIssue');
|
||||
return const ErrorMessage(message: '$kMsgError $kUnexpectedRaiseIssue');
|
||||
}
|
||||
|
||||
var body = responseModel.body;
|
||||
var formattedBody = responseModel.formattedBody;
|
||||
if (body == null) {
|
||||
return const ErrorMessage(
|
||||
message: 'Response body is missing (null). $kUnexpectedRaiseIssue');
|
||||
message: '$kMsgNullBody $kUnexpectedRaiseIssue');
|
||||
}
|
||||
if (body.isEmpty) {
|
||||
return const ErrorMessage(
|
||||
message: 'No content',
|
||||
message: kMsgNoContent,
|
||||
showIcon: false,
|
||||
showIssueButton: false,
|
||||
);
|
||||
@ -325,7 +282,7 @@ class ResponseBody extends StatelessWidget {
|
||||
if (mediaType == null) {
|
||||
return ErrorMessage(
|
||||
message:
|
||||
'Unknown Response Content-Type - ${responseModel.contentType}. $kUnexpectedRaiseIssue');
|
||||
'$kMsgUnknowContentType - ${responseModel.contentType}. $kUnexpectedRaiseIssue');
|
||||
}
|
||||
|
||||
var responseBodyView = getResponseBodyViewOptions(mediaType);
|
||||
|
@ -2,7 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class TabLabel extends StatelessWidget {
|
||||
const TabLabel({super.key, required this.text, this.showIndicator = false});
|
||||
const TabLabel({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.showIndicator = false,
|
||||
});
|
||||
final String text;
|
||||
final bool showIndicator;
|
||||
|
||||
@ -18,14 +22,14 @@ class TabLabel extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
style: kTextStyleButton,
|
||||
style: kTextStyleTab,
|
||||
),
|
||||
),
|
||||
if (showIndicator)
|
||||
const Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
padding: EdgeInsets.only(top: 1),
|
||||
child: Icon(
|
||||
Icons.circle,
|
||||
size: 6,
|
||||
|
@ -94,14 +94,39 @@ class JsonSearchField extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
return RawTextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: kCodeStyle,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search..',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RawTextField extends StatelessWidget {
|
||||
const RawTextField({
|
||||
super.key,
|
||||
this.onChanged,
|
||||
this.controller,
|
||||
this.hintText,
|
||||
this.style,
|
||||
});
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
final TextEditingController? controller;
|
||||
final String? hintText;
|
||||
final TextStyle? style;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: style,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
hintText: 'Search..',
|
||||
hintText: hintText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ dependencies:
|
||||
multi_split_view: ^2.4.0
|
||||
url_launcher: ^6.2.5
|
||||
flutter_riverpod: ^2.5.1
|
||||
riverpod: ^2.5.1
|
||||
riverpod: ^2.5.1
|
||||
uuid: ^4.3.3
|
||||
davi: ^3.4.1
|
||||
http: ^1.2.1
|
||||
|
339
test/providers/ui_providers_test.dart
Normal file
339
test/providers/ui_providers_test.dart
Normal file
@ -0,0 +1,339 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/screens/dashboard.dart';
|
||||
import 'package:apidash/screens/home_page/collection_pane.dart';
|
||||
import 'package:apidash/screens/home_page/home_page.dart';
|
||||
import 'package:apidash/screens/intro_page.dart';
|
||||
import 'package:apidash/screens/settings_page.dart';
|
||||
import 'package:apidash/services/hive_services.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUp(() async {
|
||||
const MethodChannel channel =
|
||||
MethodChannel('plugins.flutter.io/path_provider');
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(channel, (MethodCall methodCall) async {
|
||||
if (methodCall.method == 'getApplicationDocumentsDirectory') {
|
||||
// Create a mock app doc directory for testing
|
||||
Directory tempDir =
|
||||
await Directory.systemTemp.createTemp('mock_app_doc_dir');
|
||||
return tempDir.path; // Return the path to the mock directory
|
||||
}
|
||||
return null;
|
||||
});
|
||||
await openBoxes();
|
||||
});
|
||||
|
||||
group('Testing navRailIndexStateProvider', () {
|
||||
testWidgets('Dashboard should display correct initial page',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the HomePage is displayed initially
|
||||
expect(find.byType(HomePage), findsOneWidget);
|
||||
expect(find.byType(IntroPage), findsNothing);
|
||||
expect(find.byType(SettingsPage), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"Dashboard should display IntroPage when navRailIndexStateProvider is 1",
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
navRailIndexStateProvider.overrideWith((ref) => 1),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the IntroPage is displayed
|
||||
expect(find.byType(IntroPage), findsOneWidget);
|
||||
expect(find.byType(HomePage), findsNothing);
|
||||
expect(find.byType(SettingsPage), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"Dashboard should display SettingsPage when navRailIndexStateProvider is 2",
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
navRailIndexStateProvider.overrideWith((ref) => 2),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the SettingsPage is displayed
|
||||
expect(find.byType(SettingsPage), findsOneWidget);
|
||||
expect(find.byType(IntroPage), findsNothing);
|
||||
expect(find.byType(HomePage), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'navRailIndexStateProvider should update when icon button is pressed',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap on the Intro icon
|
||||
await tester.tap(find.byIcon(Icons.help_outline));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that the navRailIndexStateProvider is updated
|
||||
final dashboard = tester.element(find.byType(Dashboard));
|
||||
final container = ProviderScope.containerOf(dashboard);
|
||||
expect(container.read(navRailIndexStateProvider), 1);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'navRailIndexStateProvider should persist across widget rebuilds',
|
||||
(tester) async {
|
||||
// Pump the initial widget tree
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap on the Settings icon to change the index to 2
|
||||
await tester.tap(find.byIcon(Icons.settings_outlined));
|
||||
await tester.pump();
|
||||
|
||||
// Rebuild the widget tree with the same ProviderScope
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the navRailIndexStateProvider still has the updated value
|
||||
final dashboard = tester.element(find.byType(Dashboard));
|
||||
final container = ProviderScope.containerOf(dashboard);
|
||||
expect(container.read(navRailIndexStateProvider), 2);
|
||||
|
||||
// Verify that the SettingsPage is still displayed after the rebuild
|
||||
expect(find.byType(SettingsPage), findsOneWidget);
|
||||
expect(find.byType(IntroPage), findsNothing);
|
||||
expect(find.byType(HomePage), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'UI should update correctly when navRailIndexStateProvider changes',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Grab the Dashboard widget and its ProviderContainer
|
||||
final dashboard = tester.element(find.byType(Dashboard));
|
||||
final container = ProviderScope.containerOf(dashboard);
|
||||
|
||||
// Go to IntroPage
|
||||
container.read(navRailIndexStateProvider.notifier).state = 1;
|
||||
await tester.pump();
|
||||
|
||||
// Verify that the IntroPage is displayed
|
||||
expect(find.byType(IntroPage), findsOneWidget);
|
||||
// Verify that the selected icon is the filled version (selectedIcon)
|
||||
expect(find.byIcon(Icons.help), findsOneWidget);
|
||||
|
||||
// Go to SettingsPage
|
||||
container.read(navRailIndexStateProvider.notifier).state = 2;
|
||||
await tester.pump();
|
||||
|
||||
// Verify that the SettingsPage is displayed
|
||||
expect(find.byType(SettingsPage), findsOneWidget);
|
||||
// Verify that the selected icon is the filled version (selectedIcon)
|
||||
expect(find.byIcon(Icons.settings), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'navRailIndexStateProvider should be disposed when Dashboard is removed',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Dashboard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Grab the Dashboard widget and its ProviderContainer
|
||||
final dashboard = tester.element(find.byType(Dashboard));
|
||||
final container = ProviderScope.containerOf(dashboard);
|
||||
|
||||
// Pumping a different widget to remove the Dashboard from the widget tree
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(body: Text('Different Widget')),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the ProviderContainer has been disposed
|
||||
// by trying to read from disposed container
|
||||
bool isDisposed = false;
|
||||
try {
|
||||
container.read(navRailIndexStateProvider);
|
||||
} catch (e) {
|
||||
isDisposed = true;
|
||||
}
|
||||
expect(isDisposed, true);
|
||||
});
|
||||
});
|
||||
|
||||
group("Testing selectedIdEditStateProvider", () {
|
||||
testWidgets('It should have an initial value of null', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CollectionPane(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the initial value is null
|
||||
final collectionPane = tester.element(find.byType(CollectionPane));
|
||||
final container = ProviderScope.containerOf(collectionPane);
|
||||
expect(container.read(selectedIdEditStateProvider), null);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'selectedIdEditStateProvider should not be null after rename button has been tapped',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CollectionPane(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap on the three dots to open the request card menu
|
||||
await tester.tap(find.byType(RequestList));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(RequestItem));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.more_vert).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the "Rename" option in the menu
|
||||
await tester.tap(find.text('Rename'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify that the selectedIdEditStateProvider is not null
|
||||
final collectionPane = tester.element(find.byType(CollectionPane));
|
||||
final container = ProviderScope.containerOf(collectionPane);
|
||||
expect(container.read(selectedIdEditStateProvider), isNotNull);
|
||||
expect((container.read(selectedIdEditStateProvider)).runtimeType, String);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'It should be set back to null when user taps outside name editor',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CollectionPane(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Grab the CollectionPane widget and its ProviderContainer
|
||||
final collectionPane = tester.element(find.byType(CollectionPane));
|
||||
final container = ProviderScope.containerOf(collectionPane);
|
||||
|
||||
// Tap on the three dots to open the request card menu
|
||||
await tester.tap(find.byType(RequestList));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(RequestItem));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.more_vert).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the "Rename" option in the menu
|
||||
await tester.tap(find.text('Rename'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify that the selectedIdEditStateProvider is not null
|
||||
expect(container.read(selectedIdEditStateProvider), isNotNull);
|
||||
expect((container.read(selectedIdEditStateProvider)).runtimeType, String);
|
||||
|
||||
// Tap on the screen to simulate tapping outside the name editor
|
||||
await tester.tap(find.byType(CollectionPane));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify that the selectedIdEditStateProvider is null
|
||||
expect(container.read(selectedIdEditStateProvider), null);
|
||||
});
|
||||
testWidgets("It should be properly disposed", (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CollectionPane(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Grab the Dashboard widget and its ProviderContainer
|
||||
final collectionPane = tester.element(find.byType(CollectionPane));
|
||||
final container = ProviderScope.containerOf(collectionPane);
|
||||
|
||||
// Pumping a different widget to remove the CollectionPane from the widget tree
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(body: Text('Foo')),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that the ProviderContainer has been disposed
|
||||
// by trying to read from disposed container
|
||||
bool isDisposed = false;
|
||||
try {
|
||||
container.read(selectedIdEditStateProvider);
|
||||
} catch (e) {
|
||||
isDisposed = true;
|
||||
}
|
||||
expect(isDisposed, true);
|
||||
});
|
||||
});
|
||||
}
|
@ -21,7 +21,6 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.byType(Center), findsAtLeastNWidgets(1));
|
||||
expect(find.text('Request'), findsOneWidget);
|
||||
expect(find.text('Hide Code'), findsOneWidget);
|
||||
expect(find.text('View Code'), findsNothing);
|
||||
expect(find.text('URL Params'), findsOneWidget);
|
||||
@ -52,7 +51,6 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.byType(Center), findsAtLeastNWidgets(1));
|
||||
expect(find.text('Request'), findsOneWidget);
|
||||
expect(find.text('Hide Code'), findsOneWidget);
|
||||
expect(find.text('View Code'), findsNothing);
|
||||
expect(find.text('URL Params'), findsOneWidget);
|
||||
@ -83,7 +81,6 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.byType(Center), findsAtLeastNWidgets(1));
|
||||
expect(find.text('Request'), findsOneWidget);
|
||||
expect(find.text('Hide Code'), findsNothing);
|
||||
expect(find.text('View Code'), findsOneWidget);
|
||||
expect(find.text('URL Params'), findsOneWidget);
|
||||
@ -117,7 +114,6 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.byType(Center), findsAtLeastNWidgets(1));
|
||||
expect(find.text('Request'), findsOneWidget);
|
||||
expect(find.text('URL Params'), findsOneWidget);
|
||||
expect(find.text('Headers'), findsOneWidget);
|
||||
expect(find.text('Body'), findsOneWidget);
|
||||
|
@ -52,11 +52,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(RichText), findsAtLeastNWidgets(1));
|
||||
expect(
|
||||
find.textContaining("Response (", findRichText: true), findsOneWidget);
|
||||
expect(find.text('Hi'), findsOneWidget);
|
||||
expect(find.textContaining("200", findRichText: true), findsOneWidget);
|
||||
expect(find.textContaining("Hi", findRichText: true), findsOneWidget);
|
||||
expect(find.text(humanizeDuration(const Duration(microseconds: 23))),
|
||||
findsOneWidget);
|
||||
});
|
||||
@ -73,7 +70,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Body'), findsOneWidget);
|
||||
expect(find.text('Response Body'), findsOneWidget);
|
||||
expect(find.text('Headers'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Headers'));
|
||||
@ -81,7 +78,7 @@ void main() {
|
||||
|
||||
expect(find.text('first'), findsNothing);
|
||||
expect(find.text('second'), findsOneWidget);
|
||||
await tester.tap(find.text('Body'));
|
||||
await tester.tap(find.text('Response Body'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('first'), findsOneWidget);
|
||||
|
Reference in New Issue
Block a user