Merge branch 'main' into add-feature-pdf-preview

This commit is contained in:
Ashita Prasad
2023-09-20 07:01:44 +05:30
committed by GitHub
70 changed files with 3069 additions and 633 deletions

View File

@ -3,7 +3,7 @@ name: Bug report
about: Something wrong with API Dash. Report the problem/bug here.
title: ''
labels: bug
assignees: animator
assignees: ''
---

View File

@ -3,7 +3,7 @@ name: Feature request
about: Help us make API Dash better by suggesting new features.
title: ''
labels: enhancement
assignees: ashitaprasad
assignees: ''
---

View File

@ -4,7 +4,7 @@ about: Wanted to share something regarding API Dash that is neither a bug nor a
Don't worry, we got you covered.
title: ''
labels: ''
assignees: animator
assignees: ''
---

2
.gitignore vendored
View File

@ -49,8 +49,10 @@ linux/
macos/
windows/
web/
ios/
.vscode/*
icons/
coverage/*
installers/*
.metadata
.fvm/

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
foss42.org@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@ -4,10 +4,10 @@ We value your participation in this open source project. This page will give you
You can contribute to the project in any or all of the following ways:
- [Ask a question](https://github.com/foss42/api-dash/discussions)
- [Submit a bug report](https://github.com/foss42/api-dash/issues/new/choose)
- [Request a new feature](https://github.com/foss42/api-dash/issues/new/choose)
- [Suggest ways to improve the developer experience of an existing feature](https://github.com/foss42/api-dash/issues/new/choose)
- [Ask a question](https://github.com/foss42/apidash/discussions)
- [Submit a bug report](https://github.com/foss42/apidash/issues/new/choose)
- [Request a new feature](https://github.com/foss42/apidash/issues/new/choose)
- [Suggest ways to improve the developer experience of an existing feature](https://github.com/foss42/apidash/issues/new/choose)
- Add documentation
- Add a new feature, resolve an existing issue or add a new test to the project. (Goto [Code Contribution Guidelines](#code-contribution-guidelines)).
@ -27,11 +27,11 @@ We currently do not accept PRs that involve:
### Resolving an existing issue / Adding a requested feature
You can find all existing issues [here](https://github.com/foss42/api-dash/issues). A good place to start is to take a look at ["good first issues"](https://github.com/foss42/api-dash/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
You can find all existing issues [here](https://github.com/foss42/apidash/issues). A good place to start is to take a look at ["good first issues"](https://github.com/foss42/apidash/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
**Step 1** - Identify the issue you want to work on.
**Step 2** - Comment on the issue so that we can discuss how to approach and solve the problem.
**Step 3** - Fork the [`foss42/api-dash`](https://github.com/foss42/api-dash) repo to your account.
**Step 3** - Fork the [`foss42/apidash`](https://github.com/foss42/apidash) repo to your account.
**Step 4** - Create a new branch in your fork and name it `add-feature-xyz` or `resolve-issue-xyz`.
**Step 5** - Run API Dash locally (More details [here](#how-to-run-api-dash-locally)).
**Step 6** - Make code changes in the branch.
@ -41,8 +41,8 @@ You can find all existing issues [here](https://github.com/foss42/api-dash/issue
### Adding a new feature
**Step 1** - Open an [issue](https://github.com/foss42/api-dash/issues/new/choose) so that we can discuss on the new feature.
**Step 2** - Fork the [`foss42/api-dash`](https://github.com/foss42/api-dash) repo to your account.
**Step 1** - Open an [issue](https://github.com/foss42/apidash/issues/new/choose) so that we can discuss on the new feature.
**Step 2** - Fork the [`foss42/apidash`](https://github.com/foss42/apidash) repo to your account.
**Step 3** - Create a new branch in your fork and name it `add-feature-xyz`.
**Step 4** - Run API Dash locally (More details [here](#how-to-run-api-dash-locally)).
**Step 5** - Make the necessary code changes required to implement the feature in the branch.
@ -61,7 +61,7 @@ You can contribute by adding missing/new tests for:
- Services (`lib/services/`).
**Step 1** - Identify the test you want to add or improve.
**Step 2** - Fork the [`foss42/api-dash`](https://github.com/foss42/api-dash) repo to your account.
**Step 2** - Fork the [`foss42/apidash`](https://github.com/foss42/apidash) repo to your account.
**Step 3** - Create a new branch in your fork and name it `add-test-xyz`.
**Step 4** - Add the test to an existing test file or create a new test file in the `test` folder.
**Step 5** - Run the tests locally (More details [here](#how-to-run-tests)).
@ -70,6 +70,14 @@ You can contribute by adding missing/new tests for:
## General Instructions
### What is the supported Flutter/Dart version?
This project supports the latest Dart 3 & Flutter version. If you are using older Flutter version that does not support Dart 3, you might get errors.
In case you are setting up Flutter for the first time, just go ahead and download the latest (Stable) SDK from the [Flutter SDK Archive](https://docs.flutter.dev/release/archive). Then proceed with the Flutter installation.
In case you have already setup Flutter, make sure to switch to `stable` branch and upgrade it.
### How to run API Dash locally?
1. Fork the project.
@ -78,7 +86,7 @@ You can contribute by adding missing/new tests for:
4. This project uses [Records feature in Dart](https://github.com/dart-lang/language/blob/main/accepted/future-releases/records/records-feature-specification.md), so to run the project execute the following command:
```
flutter run --enable-experiment=records
flutter run
```
### How to run tests?
@ -86,7 +94,7 @@ flutter run --enable-experiment=records
To run tests execute the following command:
```
flutter test --enable-experiment=records --coverage
flutter test --coverage
```
To generate coverage report as html execute:
@ -95,10 +103,28 @@ To generate coverage report as html execute:
genhtml coverage/lcov.info -o coverage/html
```
*Note*: On macOS you need to have `lcov` installed on your system (`brew install lcov`) to run the above command.
**Note**: On macOS you need to have `lcov` installed on your system (`brew install lcov`) to run the above command.
To view the coverage report in the browser for further analysis, execute:
```
open coverage/html/index.html
```
#### Testing a single file
To run tests specified in a single file, execute the following command:
```
flutter test <file_path>.dart
```
Example:
```
flutter test test/widgets/codegen_previewer_test.dart
```
### How to add a new package to pubspec.yaml?
Instead of copy pasting from pub.dev, it is recommended that you use `flutter pub add package_name` to add a new package to `pubspec.yaml`. You can read more [here](https://docs.flutter.dev/packages-and-plugins/using-packages#adding-a-package-dependency-to-an-app-using-flutter-pub-add).

View File

@ -1,13 +1,13 @@
# Installation Instructions
## Windows
Download the latest Windows Installer (64 bit) from [here](https://github.com/foss42/api-dash/releases/latest)
Download the latest Windows Installer (64 bit) from [here](https://github.com/foss42/apidash/releases/latest)
To install it, simply double click on the installer and follow the step by step installation wizard.
## MacOS
Download the latest MacOS Installer (Universal - Intel and Apple Silicon) from [here](https://github.com/foss42/api-dash/releases/latest)
Download the latest MacOS Installer (Universal - Intel and Apple Silicon) from [here](https://github.com/foss42/apidash/releases/latest)
**As this app is distributed outside the App Store you have to follow the following instructions to setup and run it only for the first time.**
@ -46,7 +46,7 @@ Note: The next step has to be performed twice so that macOS adds the app to whit
### Debian-based Linux Distributions (Debian, Ubuntu, Linux Mint, etc.)
Download the `.deb` file from the [latest release](https://github.com/foss42/api-dash/releases/latest) corresponding to you CPU architecture (x64/amd64 or arm64).
Download the `.deb` file from the [latest release](https://github.com/foss42/apidash/releases/latest) corresponding to you CPU architecture (x64/amd64 or arm64).
`cd` to the Downloads folder and execute the following command to install API Dash.
@ -64,7 +64,7 @@ Launch API Dash via `apidash` command or by clicking on the API Dash app icon.
### Red Hat-based Linux Distributions (Fedora, Rocky, AlmaLinux, CentOS, RHEL, etc.)
Download the `.rpm` file from the [latest release](https://github.com/foss42/api-dash/releases/latest) corresponding to you CPU architecture (x86_64 or aarch64/arm64).
Download the `.rpm` file from the [latest release](https://github.com/foss42/apidash/releases/latest) corresponding to you CPU architecture (x86_64 or aarch64/arm64).
`cd` to the Downloads folder and execute the following command to install API Dash.

View File

@ -23,21 +23,21 @@ API Dash can be downloaded from the links below:
<tr>
<td>macOS</td>
<td><code>.dmg</code></td>
<td><a href="https://github.com/foss42/api-dash/blob/main/INSTALLATION.md#macos">Link</a></td>
<td><a href="https://github.com/foss42/apidash/blob/main/INSTALLATION.md#macos">Link</a></td>
<td>Apple Silicon & Intel</td>
<td><a href="https://bit.ly/44wmazf">Link</a></td>
</tr>
<tr>
<td>Windows</td>
<td><code>.exe</code></td>
<td><a href="https://github.com/foss42/api-dash/blob/main/INSTALLATION.md#windows">Link</a></td>
<td><a href="https://github.com/foss42/apidash/blob/main/INSTALLATION.md#windows">Link</a></td>
<td>64-bit</td>
<td><a href="https://bit.ly/424ExKb">Link</a></td>
</tr>
<tr>
<td rowspan=4>Linux</td>
<td rowspan=2><code>.deb</code></td>
<td rowspan=2><a href="https://github.com/foss42/api-dash/blob/main/INSTALLATION.md#debian-based-linux-distributions-debian-ubuntu-linux-mint-etc">Link</a></td>
<td rowspan=2><a href="https://github.com/foss42/apidash/blob/main/INSTALLATION.md#debian-based-linux-distributions-debian-ubuntu-linux-mint-etc">Link</a></td>
<td>amd64</td>
<td><a href="https://bit.ly/44sWPq2">Link</a></td>
</tr>
@ -47,7 +47,7 @@ API Dash can be downloaded from the links below:
</tr>
<tr>
<td rowspan=2><code>.rpm</code></td>
<td rowspan=2><a href="https://github.com/foss42/api-dash/blob/main/INSTALLATION.md#red-hat-based-linux-distributions-fedora-rocky-almalinux-centos-rhel-etc">Link</a></td>
<td rowspan=2><a href="https://github.com/foss42/apidash/blob/main/INSTALLATION.md#red-hat-based-linux-distributions-fedora-rocky-almalinux-centos-rhel-etc">Link</a></td>
<td>x86_64</td>
<td><a href="https://bit.ly/417gWHe">Link</a></td>
</tr>
@ -60,7 +60,9 @@ API Dash can be downloaded from the links below:
## A Quick Glimpse of API Dash ⚡️ (Demo Video)
https://github.com/foss42/api-dash/assets/615622/fccc569e-3152-47be-9f94-ceb851ee85a0
Demo Video on Youtube - [Link](https://youtu.be/IQlrgpNpS2s) (In case there is an error loading the embedded video below 👇)
https://github.com/foss42/apidash/assets/615622/fccc569e-3152-47be-9f94-ceb851ee85a0
## List of Features
@ -99,16 +101,16 @@ Visit [CHANGELOG.md](CHANGELOG.md)
## Provide Feedback, Report Bugs & Request New Features
Just click on the [Issue tab](https://github.com/foss42/api-dash/issues) to raise a new issue in this repo.
Just click on the [Issue tab](https://github.com/foss42/apidash/issues) to raise a new issue in this repo.
## Contribute to API Dash
You can contribute to API Dash in any or all of the following ways:
- [Ask a question](https://github.com/foss42/api-dash/discussions)
- [Submit a bug report](https://github.com/foss42/api-dash/issues/new/choose)
- [Request a new feature](https://github.com/foss42/api-dash/issues/new/choose)
- [Suggest ways to improve the developer experience of an existing feature](https://github.com/foss42/api-dash/issues/new/choose)
- [Ask a question](https://github.com/foss42/apidash/discussions)
- [Submit a bug report](https://github.com/foss42/apidash/issues/new/choose)
- [Request a new feature](https://github.com/foss42/apidash/issues/new/choose)
- [Suggest ways to improve the developer experience of an existing feature](https://github.com/foss42/apidash/issues/new/choose)
- Add documentation
- To add a new feature, resolve an existing issue or add a new test to the project, check out our [Contribution Guidelines](CONTRIBUTING.md).

View File

@ -40,7 +40,7 @@ Using API Dash, you can easily create & customize your API requests, visually in
7. Notification on save, download and any other user action (UX improvement).
8. Linux builds are now available for API Dash (.deb & .rpm)
.. and various bug fixes & performance improvements. View full changelog [here](https://github.com/foss42/api-dash/blob/main/CHANGELOG.md).
.. and various bug fixes & performance improvements. View full changelog [here](https://github.com/foss42/apidash/blob/main/CHANGELOG.md).
#br
#br

View File

@ -1 +1,28 @@
export 'dart/pkg_http.dart';
import 'package:apidash/codegen/kotlin/pkg_okhttp.dart';
import 'python/pkg_http_client.dart';
import 'package:apidash/codegen/python/pkg_request.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/models/models.dart' show RequestModel;
import 'dart/pkg_http.dart';
class Codegen {
String? getCode(
CodegenLanguage codegenLanguage,
RequestModel requestModel,
String defaultUriScheme,
) {
switch (codegenLanguage) {
case CodegenLanguage.dartHttp:
return DartHttpCodeGen().getCode(requestModel, defaultUriScheme);
case CodegenLanguage.kotlinOkHttp:
return KotlinOkHttpCodeGen().getCode(requestModel);
case CodegenLanguage.pythonHttpClient:
return PythonHttpClient().getCode(requestModel);
case CodegenLanguage.pythonRequests:
return PythonRequestCodeGen().getCode(requestModel, defaultUriScheme);
default:
throw ArgumentError('Invalid codegenLanguage');
}
}
}

View File

@ -0,0 +1,80 @@
import 'package:apidash/consts.dart';
import '../../models/request_model.dart';
class KotlinOkHttpCodeGen {
final String headerSnippet = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
""";
final String footerSnippet = """ .build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
String getCode(RequestModel requestModel) {
String result = "";
result = result + headerSnippet;
if (requestModel.method != HTTPVerb.get &&
requestModel.method != HTTPVerb.head) {
result =
"""${result}val mediaType = "${requestModel.requestBodyContentType == ContentType.json ? "application/json" : "text/plain"}".toMediaType()
val body = "${requestModel.requestBody}".toRequestBody(mediaType)\n""";
}
result = "${result}val request = Request.Builder()\n";
result = "$result .url(\"${requestModel.url}\")\n";
result = result + addQueryParams(requestModel);
result = result + addRequestMethod(requestModel);
result = result + addHeaders(requestModel);
result = result + footerSnippet;
return result;
}
String addQueryParams(RequestModel requestModel) {
String result = "";
if (requestModel.requestParams == null) {
return result;
}
for (final queryParam in requestModel.requestParams!) {
result =
"""$result .addQueryParameter("${queryParam.name}", "${queryParam.value}")\n""";
}
return result;
}
String addHeaders(RequestModel requestModel) {
String result = "";
if (requestModel.requestHeaders == null) {
return result;
}
for (final header in requestModel.requestHeaders!) {
result = """$result .addHeader("${header.name}", "${header.value}")\n""";
}
return result;
}
String addRequestMethod(RequestModel requestModel) {
String result = "";
if (requestModel.method != HTTPVerb.get &&
requestModel.method != HTTPVerb.head &&
requestModel.method != HTTPVerb.delete) {
result = """$result .${requestModel.method.name}(body)\n""";
} else if (requestModel.method == HTTPVerb.head) {
result = """$result .${requestModel.method.name}()\n""";
}
if (requestModel.method == HTTPVerb.delete) {
result = """$result .method("DELETE", body)\n""";
}
return result;
}
}

View File

@ -0,0 +1,82 @@
import 'package:apidash/consts.dart';
import '../../models/request_model.dart';
class PythonHttpClient {
final String headerSnippet = """
import http.client
import json
""";
final String footerSnippet = """
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
""";
String getCode(RequestModel requestModel) {
String result = "";
result += headerSnippet;
result +=
"conn = http.client.HTTPSConnection('${getUrl(requestModel)["host"]}'${getUrl(requestModel)["port"]})\n";
result += "payload = json.dumps(${requestModel.requestBody ?? ""})\n";
result +=
"""headers = {\n'Content-Type':'${requestModel.requestBodyContentType == ContentType.json ? 'application/json' : 'text/plain'}'\n${addHeaders(requestModel)}},\n""";
result +=
"conn.request(\"${requestModel.method.name.toUpperCase()}\", \"${getUrl(requestModel)["endpoint"]}${addQueryParams(requestModel)}\", payload, headers)\n";
result += footerSnippet;
return result;
}
String addHeaders(RequestModel requestModel) {
String result = "";
if (requestModel.requestHeaders == null) {
return result;
}
for (final header in requestModel.requestHeaders!) {
result += """'${header.name}':'${header.value}',\n""";
}
return result;
}
String addQueryParams(RequestModel requestModel) {
String result = "";
if (requestModel.requestParams == null) {
return result;
}
result += "?";
for (final queryParam in requestModel.requestParams!) {
result +=
"${queryParam.name.toString().replaceAll(" ", "%20")}=${queryParam.value.toString().replaceAll(" ", "%20")}&";
}
return result.substring(0, result.length - 1);
}
Map<String, String> getUrl(RequestModel requestModel) {
String result = "";
if (requestModel.url.startsWith('http://') ||
requestModel.url.startsWith('https://')) {
result = requestModel.url.substring(requestModel.url.indexOf('://') + 3);
} else {
result = requestModel.url;
}
Map<String, String> resultMap = {};
if (result.contains(":")) {
resultMap["host"] = result.substring(0, result.indexOf(':'));
resultMap["port"] =
",${result.substring(result.indexOf(':') + 1, result.contains('/') ? result.indexOf('/') : result.length)}";
resultMap["endpoint"] =
"/${result.substring(result.contains('/') ? result.indexOf('/') + 1 : result.length)}";
} else {
resultMap["host"] = result.contains("/")
? result.substring(0, result.indexOf('/'))
: result;
resultMap["port"] = "";
resultMap["endpoint"] =
"/${result.substring(result.contains('/') ? result.indexOf('/') + 1 : result.length)}";
}
return resultMap;
}
}

View File

@ -0,0 +1,104 @@
import 'package:jinja/jinja.dart' as jj;
import 'package:apidash/models/models.dart' show RequestModel;
import 'package:apidash/utils/utils.dart' show padMultilineString, rowsToMap;
import 'package:apidash/consts.dart';
class PythonRequestCodeGen {
int kHeadersPadding = 16;
String kPythonTemplate = '''
import requests
def main():
url = '{{url}}'{{params}}{{body}}{{headers}}
response = requests.{{method}}(
url{{request_params}}{{request_headers}}{{request_body}}
)
status_code = response.status_code
if 200 <= status_code < 300:
print('Status Code:', status_code)
print('Response Body:', response.text)
else:
print('Error Status Code:', status_code)
print('Error Response Body:', response.reason)
main()
''';
String? getCode(RequestModel requestModel, String defaultUriScheme) {
try {
bool hasHeaders = false;
bool hasBody = false;
bool hasParams = false;
String url = requestModel.url;
if (!url.contains('://') && url.isNotEmpty) {
url = '$defaultUriScheme://$url';
}
var paramsList = requestModel.requestParams;
String params = '';
if (paramsList != null) {
for (var param in paramsList) {
if (param.name.isNotEmpty) {
hasParams = true;
params += '\n "${param.name}": "${param.value}",';
}
}
}
var method = requestModel.method.name.toLowerCase();
var requestBody = requestModel.requestBody;
String requestBodyString = '';
if (requestBody != null && requestBody.isNotEmpty) {
hasBody = true;
var bodyType = requestModel.requestBodyContentType;
if (bodyType == ContentType.json) {
int index = requestBody.lastIndexOf("}");
index--;
while (requestBody[index] == " " || requestBody[index] == "\n") {
index--;
}
if (requestBody[index] == ",") {
requestBody = requestBody.substring(0, index) +
requestBody.substring(index + 1);
}
}
requestBodyString = "data = '''$requestBody'''";
}
var headersList = requestModel.requestHeaders;
String headers = '';
if (headersList != null || hasBody) {
var head = rowsToMap(requestModel.requestHeaders) ?? {};
if (head.isNotEmpty || hasBody) {
if (hasBody) {
head["content-type"] =
kContentTypeMap[requestModel.requestBodyContentType] ?? "";
}
headers = kEncoder.convert(head);
headers = padMultilineString(headers, kHeadersPadding);
}
hasHeaders = headers.isNotEmpty;
}
var template = jj.Template(kPythonTemplate);
var pythonCode = template.render({
'url': url,
'params': hasParams ? '\n\n params = {$params \n }' : '',
'body': hasBody ? '\n\n $requestBodyString' : '',
'headers': hasHeaders ? '\n\n headers = $headers' : '',
'method': method,
'request_params': hasParams ? ', params=params' : '',
'request_headers': hasHeaders ? ', headers=headers' : '',
'request_body': hasBody ? ', data=data' : '',
});
return pythonCode;
} catch (e) {
return null;
}
}
}

View File

@ -6,7 +6,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:davi/davi.dart';
const kDiscordUrl = "https://bit.ly/heyfoss";
const kGitUrl = "https://github.com/foss42/api-dash";
const kGitUrl = "https://github.com/foss42/apidash";
const kIssueUrl = "$kGitUrl/issues";
final kIsMacOS = !kIsWeb && Platform.isMacOS;
@ -28,6 +28,7 @@ const kWindowTitle = "API Dash";
const kMinWindowSize = Size(900, 600);
const kMinInitialWindowWidth = 1200.0;
const kMinInitialWindowHeight = 800.0;
const kMinRequestEditorDetailsCardPaneSize = 300.0;
const kColorSchemeSeed = Colors.blue;
final kFontFamily = GoogleFonts.openSans().fontFamily;
@ -225,9 +226,22 @@ const kMethodsWithBody = [
HTTPVerb.patch,
HTTPVerb.delete,
];
const kDefaultHttpMethod = HTTPVerb.get;
const kDefaultContentType = ContentType.json;
enum CodegenLanguage {
dartHttp("Dart (http)", "dart", "dart"),
kotlinOkHttp("Kotlin (okhttp3)", "java", "kt"),
pythonHttpClient("Python (http.client)", "python", "py"),
pythonRequests("Python (requests)", "python", "py");
const CodegenLanguage(this.label, this.codeHighlightLang, this.ext);
final String label;
final String codeHighlightLang;
final String ext;
}
const JsonEncoder kEncoder = JsonEncoder.withIndent(' ');
const LineSplitter kSplitter = LineSplitter();
@ -311,7 +325,7 @@ const Map<String, Map<String, List<ResponseBodyView>>>
kSubTypeSvg: kCodeRawBodyViewOptions,
},
kTypeAudio: {
kSubTypeDefaultViewOptions: kNoBodyViewOptions,
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
},
kTypeVideo: {
kSubTypeDefaultViewOptions: kNoBodyViewOptions,
@ -427,6 +441,9 @@ const kImageError =
const kPdfError =
"There seems to be an issue rendering this pdf. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
const kAudioError =
"There seems to be an issue playing this audio. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
const kRaiseIssue =
"\nPlease raise an issue in API Dash GitHub repo so that we can resolve it.";

View File

@ -13,7 +13,7 @@ void main() async {
await setupInitialWindow();
} else {
var win = getInitialSize();
await setupWindow(sz: win.$0, off: win.$1);
await setupWindow(sz: win.$1, off: win.$2);
}
runApp(
ProviderScope(

View File

@ -1,38 +0,0 @@
import 'package:flutter/material.dart';
@immutable
class KVRow {
const KVRow(this.k, this.v);
final String k;
final dynamic v;
KVRow copyWith({
String? k,
dynamic v,
}) {
return KVRow(k ?? this.k, v ?? this.v);
}
@override
String toString() {
return {k: v}.toString();
}
@override
bool operator ==(Object other) {
return other is KVRow &&
other.runtimeType == runtimeType &&
other.k == k &&
other.v == v;
}
@override
int get hashCode {
return Object.hash(
runtimeType,
k,
v,
);
}
}

View File

@ -1,4 +1,4 @@
export 'kvrow_model.dart';
export 'name_value_model.dart';
export 'request_model.dart';
export 'response_model.dart';
export 'settings_model.dart';

View File

@ -0,0 +1,17 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'name_value_model.freezed.dart';
part 'name_value_model.g.dart';
@freezed
class NameValueModel with _$NameValueModel {
const factory NameValueModel({
required String name,
required dynamic value,
}) = _NameValueModel;
factory NameValueModel.fromJson(Map<String, Object?> json) =>
_$NameValueModelFromJson(json);
}

View File

@ -0,0 +1,181 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'name_value_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
NameValueModel _$NameValueModelFromJson(Map<String, dynamic> json) {
return _NameValueModel.fromJson(json);
}
/// @nodoc
mixin _$NameValueModel {
String get name => throw _privateConstructorUsedError;
dynamic get value => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$NameValueModelCopyWith<NameValueModel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $NameValueModelCopyWith<$Res> {
factory $NameValueModelCopyWith(
NameValueModel value, $Res Function(NameValueModel) then) =
_$NameValueModelCopyWithImpl<$Res, NameValueModel>;
@useResult
$Res call({String name, dynamic value});
}
/// @nodoc
class _$NameValueModelCopyWithImpl<$Res, $Val extends NameValueModel>
implements $NameValueModelCopyWith<$Res> {
_$NameValueModelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? value = freezed,
}) {
return _then(_value.copyWith(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
value: freezed == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as dynamic,
) as $Val);
}
}
/// @nodoc
abstract class _$$_NameValueModelCopyWith<$Res>
implements $NameValueModelCopyWith<$Res> {
factory _$$_NameValueModelCopyWith(
_$_NameValueModel value, $Res Function(_$_NameValueModel) then) =
__$$_NameValueModelCopyWithImpl<$Res>;
@override
@useResult
$Res call({String name, dynamic value});
}
/// @nodoc
class __$$_NameValueModelCopyWithImpl<$Res>
extends _$NameValueModelCopyWithImpl<$Res, _$_NameValueModel>
implements _$$_NameValueModelCopyWith<$Res> {
__$$_NameValueModelCopyWithImpl(
_$_NameValueModel _value, $Res Function(_$_NameValueModel) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? name = null,
Object? value = freezed,
}) {
return _then(_$_NameValueModel(
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
value: freezed == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_NameValueModel
with DiagnosticableTreeMixin
implements _NameValueModel {
const _$_NameValueModel({required this.name, required this.value});
factory _$_NameValueModel.fromJson(Map<String, dynamic> json) =>
_$$_NameValueModelFromJson(json);
@override
final String name;
@override
final dynamic value;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'NameValueModel(name: $name, value: $value)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'NameValueModel'))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('value', value));
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_NameValueModel &&
(identical(other.name, name) || other.name == name) &&
const DeepCollectionEquality().equals(other.value, value));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, name, const DeepCollectionEquality().hash(value));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_NameValueModelCopyWith<_$_NameValueModel> get copyWith =>
__$$_NameValueModelCopyWithImpl<_$_NameValueModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_NameValueModelToJson(
this,
);
}
}
abstract class _NameValueModel implements NameValueModel {
const factory _NameValueModel(
{required final String name,
required final dynamic value}) = _$_NameValueModel;
factory _NameValueModel.fromJson(Map<String, dynamic> json) =
_$_NameValueModel.fromJson;
@override
String get name;
@override
dynamic get value;
@override
@JsonKey(ignore: true)
_$$_NameValueModelCopyWith<_$_NameValueModel> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'name_value_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_NameValueModel _$$_NameValueModelFromJson(Map<String, dynamic> json) =>
_$_NameValueModel(
name: json['name'] as String,
value: json['value'],
);
Map<String, dynamic> _$$_NameValueModelToJson(_$_NameValueModel instance) =>
<String, dynamic>{
'name': instance.name,
'value': instance.value,
};

View File

@ -1,21 +1,21 @@
import 'package:flutter/foundation.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart' show mapToRows, rowsToMap;
import 'kvrow_model.dart';
import 'name_value_model.dart';
import 'response_model.dart';
@immutable
class RequestModel {
const RequestModel({
required this.id,
this.method = kDefaultHttpMethod,
this.method = HTTPVerb.get,
this.url = "",
this.name = "",
this.description = "",
this.requestTabIndex = 0,
this.requestHeaders,
this.requestParams,
this.requestBodyContentType = kDefaultContentType,
this.requestBodyContentType = ContentType.json,
this.requestBody,
this.responseStatus,
this.message,
@ -28,8 +28,8 @@ class RequestModel {
final String name;
final String description;
final int requestTabIndex;
final List<KVRow>? requestHeaders;
final List<KVRow>? requestParams;
final List<NameValueModel>? requestHeaders;
final List<NameValueModel>? requestParams;
final ContentType requestBodyContentType;
final String? requestBody;
final int? responseStatus;
@ -59,8 +59,8 @@ class RequestModel {
String? name,
String? description,
int? requestTabIndex,
List<KVRow>? requestHeaders,
List<KVRow>? requestParams,
List<NameValueModel>? requestHeaders,
List<NameValueModel>? requestParams,
ContentType? requestBodyContentType,
String? requestBody,
int? responseStatus,

View File

@ -96,8 +96,8 @@ class CollectionStateNotifier extends StateNotifier<List<RequestModel>?> {
String? name,
String? description,
int? requestTabIndex,
List<KVRow>? requestHeaders,
List<KVRow>? requestParams,
List<NameValueModel>? requestHeaders,
List<NameValueModel>? requestParams,
ContentType? requestBodyContentType,
String? requestBody,
int? responseStatus,
@ -132,17 +132,17 @@ class CollectionStateNotifier extends StateNotifier<List<RequestModel>?> {
var responseRec =
await request(requestModel, defaultUriScheme: defaultUriScheme);
late final RequestModel newRequestModel;
if (responseRec.$0 == null) {
if (responseRec.$1 == null) {
newRequestModel = requestModel.copyWith(
responseStatus: -1,
message: responseRec.$2,
message: responseRec.$3,
);
} else {
final responseModel = baseResponseModel.fromResponse(
response: responseRec.$0!,
time: responseRec.$1!,
response: responseRec.$1!,
time: responseRec.$2!,
);
int statusCode = responseRec.$0!.statusCode;
int statusCode = responseRec.$1!.statusCode;
newRequestModel = requestModel.copyWith(
responseStatus: statusCode,
message: kResponseCodeReasons[statusCode],
@ -203,4 +203,10 @@ class CollectionStateNotifier extends StateNotifier<List<RequestModel>?> {
await hiveHandler.removeUnused();
ref.read(saveDataStateProvider.notifier).update((state) => false);
}
Map<String, dynamic> exportData() {
return {
"data": state!.map((e) => e.toJson(includeResponse: false)).toList()
};
}
}

View File

@ -1,3 +1,4 @@
import 'package:apidash/consts.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final navRailIndexStateProvider = StateProvider<int?>((ref) => 0);
@ -7,3 +8,4 @@ final sentRequestIdStateProvider = StateProvider<String?>((ref) => null);
final codePaneVisibleStateProvider = StateProvider<bool>((ref) => false);
final saveDataStateProvider = StateProvider<bool>((ref) => false);
final clearDataStateProvider = StateProvider<bool>((ref) => false);
final codegenLanguageStateProvider = StateProvider<CodegenLanguage>((ref) => CodegenLanguage.dartHttp);

View File

@ -82,11 +82,15 @@ class _DashboardState extends ConsumerState<Dashboard> {
color: Theme.of(context).colorScheme.surfaceVariant,
),
Expanded(
child: <int?, Widget>{
null: const SettingsPage(),
0: const IntroPage(),
1: const HomePage()
}[railIdx]!,
child: IndexedStack(
alignment: AlignmentDirectional.topCenter,
index: railIdx == null ? 0 : railIdx + 1,
children: const [
SettingsPage(),
IntroPage(),
HomePage(),
],
),
)
],
),

View File

@ -13,7 +13,7 @@ class CodePane extends ConsumerStatefulWidget {
}
class _CodePaneState extends ConsumerState<CodePane> {
final DartHttpCodeGen dartHttpCodeGen = DartHttpCodeGen();
final Codegen codegen = Codegen();
@override
void initState() {
@ -22,10 +22,15 @@ class _CodePaneState extends ConsumerState<CodePane> {
@override
Widget build(BuildContext context) {
final CodegenLanguage codegenLanguage =
ref.watch(codegenLanguageStateProvider);
final activeRequestModel = ref.watch(activeRequestModelProvider);
final defaultUriScheme =
ref.watch(settingsProvider.select((value) => value.defaultUriScheme));
final code = dartHttpCodeGen.getCode(activeRequestModel!, defaultUriScheme);
final code =
codegen.getCode(codegenLanguage, activeRequestModel!, defaultUriScheme);
if (code == null) {
return const ErrorMessage(
message: "An error was encountered while generating code. $kRaiseIssue",
@ -33,6 +38,12 @@ class _CodePaneState extends ConsumerState<CodePane> {
}
return ViewCodePane(
code: code,
codegenLanguage: codegenLanguage,
onChangedCodegenLanguage: (CodegenLanguage? value) {
ref
.read(codegenLanguageStateProvider.notifier)
.update((state) => value!);
},
);
}
}

View File

@ -15,7 +15,7 @@ class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeIdStateProvider);
final reqestModel = ref
final requestModel = ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(activeId!);
return Container(
@ -25,11 +25,11 @@ class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
margin: kPt5o10,
child: Column(
children: [
SizedBox(
const SizedBox(
height: kHeaderHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
children: [
Text(
"Select Content Type:",
),
@ -41,7 +41,7 @@ class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
child: TextFieldEditor(
key: Key("$activeId-body"),
fieldKey: "$activeId-body-editor",
initialValue: reqestModel.requestBody,
initialValue: requestModel.requestBody,
onChanged: (String value) {
ref
.read(collectionStateNotifierProvider.notifier)

View File

@ -15,7 +15,7 @@ class EditRequestHeaders extends ConsumerStatefulWidget {
}
class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
late List<KVRow> rows;
late List<NameValueModel> rows;
final random = Random.secure();
late int seed;
@ -37,9 +37,11 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
final length = ref.watch(activeRequestModelProvider
.select((value) => value?.requestHeaders?.length));
var rH = ref.read(activeRequestModelProvider)?.requestHeaders;
rows = (rH == null || rH.isEmpty) ? [const KVRow("", "")] : rH;
rows = (rH == null || rH.isEmpty)
? [const NameValueModel(name: "", value: "")]
: rH;
DaviModel<KVRow> model = DaviModel<KVRow>(
DaviModel<NameValueModel> model = DaviModel<NameValueModel>(
rows: rows,
columns: [
DaviColumn(
@ -49,10 +51,10 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
int idx = row.index;
return CellField(
keyId: "$activeId-$idx-headers-k-$seed",
initialValue: rows[idx].k,
initialValue: rows[idx].name,
hintText: "Add Header Name",
onChanged: (value) {
rows[idx] = rows[idx].copyWith(k: value);
rows[idx] = rows[idx].copyWith(name: value);
_onFieldChange(activeId!);
},
colorScheme: Theme.of(context).colorScheme,
@ -76,10 +78,10 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
int idx = row.index;
return CellField(
keyId: "$activeId-$idx-headers-v-$seed",
initialValue: rows[idx].v,
initialValue: rows[idx].value,
hintText: " Add Header Value",
onChanged: (value) {
rows[idx] = rows[idx].copyWith(v: value);
rows[idx] = rows[idx].copyWith(value: value);
_onFieldChange(activeId!);
},
colorScheme: Theme.of(context).colorScheme,
@ -96,6 +98,9 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
? kIconRemoveDark
: kIconRemoveLight,
onTap: () {
if (rows.length == 1) {
return;
}
rows.removeAt(row.index);
seed = random.nextInt(kRandMax);
_onFieldChange(activeId!);
@ -118,7 +123,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
Expanded(
child: DaviTheme(
data: kTableThemeData,
child: Davi<KVRow>(model),
child: Davi<NameValueModel>(model),
),
),
],
@ -130,7 +135,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
padding: const EdgeInsets.only(bottom: 30),
child: ElevatedButton.icon(
onPressed: () {
rows.add(const KVRow("", ""));
rows.add(const NameValueModel(name: "", value: ""));
_onFieldChange(activeId!);
},
icon: const Icon(Icons.add),

View File

@ -16,7 +16,7 @@ class EditRequestURLParams extends ConsumerStatefulWidget {
}
class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
late List<KVRow> rows;
late List<NameValueModel> rows;
final random = Random.secure();
late int seed;
@ -38,9 +38,11 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
final length = ref.watch(activeRequestModelProvider
.select((value) => value?.requestParams?.length));
var rP = ref.read(activeRequestModelProvider)?.requestParams;
rows = (rP == null || rP.isEmpty) ? [const KVRow("", "")] : rP;
rows = (rP == null || rP.isEmpty)
? [const NameValueModel(name: "", value: "")]
: rP;
DaviModel<KVRow> model = DaviModel<KVRow>(
DaviModel<NameValueModel> model = DaviModel<NameValueModel>(
rows: rows,
columns: [
DaviColumn(
@ -50,10 +52,10 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
int idx = row.index;
return CellField(
keyId: "$activeId-$idx-params-k-$seed",
initialValue: rows[idx].k,
initialValue: rows[idx].name,
hintText: "Add URL Parameter",
onChanged: (value) {
rows[idx] = rows[idx].copyWith(k: value);
rows[idx] = rows[idx].copyWith(name: value);
_onFieldChange(activeId!);
},
colorScheme: Theme.of(context).colorScheme,
@ -77,10 +79,10 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
int idx = row.index;
return CellField(
keyId: "$activeId-$idx-params-v-$seed",
initialValue: rows[idx].v,
initialValue: rows[idx].value,
hintText: "Add Value",
onChanged: (value) {
rows[idx] = rows[idx].copyWith(v: value);
rows[idx] = rows[idx].copyWith(value: value);
_onFieldChange(activeId!);
},
colorScheme: Theme.of(context).colorScheme,
@ -97,6 +99,9 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
? kIconRemoveDark
: kIconRemoveLight,
onTap: () {
if (rows.length == 1) {
return;
}
rows.removeAt(row.index);
seed = random.nextInt(kRandMax);
_onFieldChange(activeId!);
@ -119,7 +124,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
Expanded(
child: DaviTheme(
data: kTableThemeData,
child: Davi<KVRow>(model),
child: Davi<NameValueModel>(model),
),
),
],
@ -131,7 +136,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
padding: const EdgeInsets.only(bottom: 30),
child: ElevatedButton.icon(
onPressed: () {
rows.add(const KVRow("", ""));
rows.add(const NameValueModel(name: "", value: ""));
_onFieldChange(activeId!);
},
icon: const Icon(Icons.add),

View File

@ -28,8 +28,8 @@ class _RequestEditorPaneState extends ConsumerState<RequestEditorPane> {
} else {
return Padding(
padding: kIsMacOS ? kPt24o8 : kP8,
child: Column(
children: const [
child: const Column(
children: [
EditorPaneRequestURLCard(),
kVSpacer10,
Expanded(

View File

@ -28,13 +28,13 @@ class _EditorPaneRequestURLCardState extends State<EditorPaneRequestURLCard> {
),
borderRadius: kBorderRadius12,
),
child: Padding(
padding: const EdgeInsets.symmetric(
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 20,
),
child: Row(
children: const [
children: [
DropdownButtonHTTPMethod(),
kHSpacer20,
Expanded(

View File

@ -13,8 +13,8 @@ class HomePage extends StatefulWidget {
class HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Column(
children: const [
return const Column(
children: [
Expanded(
child: DashboardSplitView(
sidebarWidget: CollectionPane(),

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
class IntroPage extends StatefulWidget {
const IntroPage({super.key});
@ -12,9 +11,6 @@ class IntroPage extends StatefulWidget {
class _IntroPageState extends State<IntroPage> {
@override
Widget build(BuildContext context) {
return const Padding(
padding: kPh60,
child: IntroMessage(),
);
return const IntroMessage();
}
}

View File

@ -1,7 +1,8 @@
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 '../providers/providers.dart';
import '../widgets/widgets.dart';
import '../utils/utils.dart';
import 'package:apidash/consts.dart';
class SettingsPage extends ConsumerStatefulWidget {
@ -73,6 +74,24 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
.update(saveResponses: value);
},
),
ListTile(
contentPadding: EdgeInsets.zero,
hoverColor: kColorTransparent,
title: const Text('Export Collection'),
subtitle: const Text('Export your collection to a JSON file'),
trailing: FilledButton(
onPressed: () async {
var data = ref
.read(collectionStateNotifierProvider.notifier)
.exportData();
var pth = await getFileDownloadpath(null, "json");
if (pth != null) {
await saveFile(pth, jsonMapToBytes(data));
}
},
child: const Text("Export Data"),
),
),
ListTile(
contentPadding: EdgeInsets.zero,
hoverColor: kColorTransparent,

View File

@ -7,66 +7,59 @@ import 'package:apidash/models/models.dart';
import 'package:apidash/consts.dart';
Future<(http.Response?, Duration?, String?)> request(
RequestModel requestModel,
{String defaultUriScheme = kDefaultUriScheme}
) async {
(Uri?, String?) uriRec = getValidRequestUri(requestModel.url,
requestModel.requestParams,
defaultUriScheme: defaultUriScheme);
if(uriRec.$0 != null){
Uri requestUrl = uriRec.$0!;
RequestModel requestModel, {
String defaultUriScheme = kDefaultUriScheme,
}) async {
(Uri?, String?) uriRec = getValidRequestUri(
requestModel.url,
requestModel.requestParams,
defaultUriScheme: defaultUriScheme,
);
if (uriRec.$1 != null) {
Uri requestUrl = uriRec.$1!;
Map<String, String> headers = rowsToMap(requestModel.requestHeaders) ?? {};
http.Response response;
String? body;
try {
try {
var requestBody = requestModel.requestBody;
if(kMethodsWithBody.contains(requestModel.method) && requestBody != null){
if (kMethodsWithBody.contains(requestModel.method) &&
requestBody != null) {
var contentLength = utf8.encode(requestBody).length;
if (contentLength > 0){
if (contentLength > 0) {
body = requestBody;
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
headers[HttpHeaders.contentTypeHeader] = kContentTypeMap[requestModel.requestBodyContentType] ?? "";
headers[HttpHeaders.contentTypeHeader] =
kContentTypeMap[requestModel.requestBodyContentType] ?? "";
}
}
Stopwatch stopwatch = Stopwatch()..start();
switch(requestModel.method){
switch (requestModel.method) {
case HTTPVerb.get:
response = await http.get(requestUrl,
headers: headers);
response = await http.get(requestUrl, headers: headers);
break;
case HTTPVerb.head:
response = await http.head(requestUrl,
headers: headers);
response = await http.head(requestUrl, headers: headers);
break;
case HTTPVerb.post:
response = await http.post(requestUrl,
headers: headers,
body: body);
response = await http.post(requestUrl, headers: headers, body: body);
break;
case HTTPVerb.put:
response = await http.put(requestUrl,
headers: headers,
body: body);
response = await http.put(requestUrl, headers: headers, body: body);
break;
case HTTPVerb.patch:
response = await http.patch(requestUrl,
headers: headers,
body: body);
response = await http.patch(requestUrl, headers: headers, body: body);
break;
case HTTPVerb.delete:
response = await http.delete(requestUrl,
headers: headers,
body: body);
response =
await http.delete(requestUrl, headers: headers, body: body);
break;
}
stopwatch.stop();
stopwatch.stop();
return (response, stopwatch.elapsed, null);
}
catch (e) {
} catch (e) {
return (null, null, e.toString());
}
}
else {
return (null, null, uriRec.$1);
} else {
return (null, null, uriRec.$2);
}
}

View File

@ -1,7 +1,7 @@
import 'dart:typed_data';
import 'dart:convert';
import '../models/models.dart';
import '../consts.dart';
import 'package:apidash/models/models.dart' show KVRow;
String humanizeDuration(Duration? duration) {
if (duration == null) {
@ -24,6 +24,14 @@ String humanizeDuration(Duration? duration) {
}
}
String audioPosition(Duration? duration) {
if (duration == null) return "";
var min = duration.inMinutes;
var secs = duration.inSeconds.remainder(60);
var secondsPadding = secs < 10 ? "0" : "";
return "$min:$secondsPadding$secs";
}
String capitalizeFirstLetter(String? text) {
if (text == null || text == "") {
return "";
@ -52,30 +60,31 @@ String padMultilineString(String text, int padding,
return lines.join("\n");
}
Map<String, String>? rowsToMap(List<KVRow>? kvRows, {bool isHeader = false}) {
Map<String, String>? rowsToMap(List<NameValueModel>? kvRows,
{bool isHeader = false}) {
if (kvRows == null) {
return null;
}
Map<String, String> finalMap = {};
for (var row in kvRows) {
if (row.k.trim() != "") {
String key = row.k;
if (row.name.trim() != "") {
String key = row.name;
if (isHeader) {
key = key.toLowerCase();
}
finalMap[key] = row.v.toString();
finalMap[key] = row.value.toString();
}
}
return finalMap;
}
List<KVRow>? mapToRows(Map<String, String>? kvMap) {
List<NameValueModel>? mapToRows(Map<String, String>? kvMap) {
if (kvMap == null) {
return null;
}
List<KVRow> finalRows = [];
List<NameValueModel> finalRows = [];
for (var k in kvMap.keys) {
finalRows.add(KVRow(k, kvMap[k]));
finalRows.add(NameValueModel(name: k, value: kvMap[k]));
}
return finalRows;
}
@ -89,3 +98,14 @@ Uint8List? stringToBytes(String? text) {
return bytes;
}
}
Uint8List jsonMapToBytes(Map<String, dynamic>? map) {
if (map == null) {
return Uint8List.fromList([]);
} else {
String text = kEncoder.convert(map);
var l = utf8.encode(text);
var bytes = Uint8List.fromList(l);
return bytes;
}
}

View File

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:collection/collection.dart' show mergeMaps;
import 'package:http_parser/http_parser.dart';
import 'package:xml/xml.dart';
import 'package:apidash/models/models.dart' show KVRow;
import '../models/models.dart';
import 'convert_utils.dart' show rowsToMap;
import '../consts.dart';
@ -44,8 +44,8 @@ MediaType? getMediaTypeFromHeaders(Map? headers) {
}
(String?, bool) getUriScheme(Uri uri) {
if(uri.hasScheme){
if(kSupportedUriSchemes.contains(uri.scheme)){
if (uri.hasScheme) {
if (kSupportedUriSchemes.contains(uri.scheme)) {
return (uri.scheme, true);
}
return (uri.scheme, false);
@ -54,37 +54,34 @@ MediaType? getMediaTypeFromHeaders(Map? headers) {
}
(Uri?, String?) getValidRequestUri(
String? url,
List<KVRow>? requestParams,
{String defaultUriScheme = kDefaultUriScheme}
) {
String? url, List<NameValueModel>? requestParams,
{String defaultUriScheme = kDefaultUriScheme}) {
url = url?.trim();
if(url == null || url == ""){
if (url == null || url == "") {
return (null, "URL is missing!");
}
Uri? uri = Uri.tryParse(url);
if(uri == null){
Uri? uri = Uri.tryParse(url);
if (uri == null) {
return (null, "Check URL (malformed)");
}
(String?, bool) urlScheme = getUriScheme(uri);
if(urlScheme.$0 != null){
if (!urlScheme.$1){
return (null, "Unsupported URL Scheme (${urlScheme.$0})");
if (urlScheme.$1 != null) {
if (!urlScheme.$2) {
return (null, "Unsupported URL Scheme (${urlScheme.$1})");
}
}
else {
} else {
url = "$defaultUriScheme://$url";
}
uri = Uri.parse(url);
if (uri.hasFragment){
uri = Uri.parse(url);
if (uri.hasFragment) {
uri = uri.removeFragment();
}
Map<String, String>? queryParams = rowsToMap(requestParams);
if(queryParams != null){
if(uri.hasQuery){
if (queryParams != null) {
if (uri.hasQuery) {
Map<String, String> urlQueryParams = uri.queryParameters;
queryParams = mergeMaps(urlQueryParams, queryParams);
}
@ -93,48 +90,58 @@ MediaType? getMediaTypeFromHeaders(Map? headers) {
return (uri, null);
}
(List<ResponseBodyView>, String?) getResponseBodyViewOptions(MediaType? mediaType){
if(mediaType != null){
(List<ResponseBodyView>, String?) getResponseBodyViewOptions(
MediaType? mediaType) {
if (mediaType != null) {
var type = mediaType.type;
var subtype = mediaType.subtype;
if(kResponseBodyViewOptions.containsKey(type)){
if (kResponseBodyViewOptions[type]!.containsKey(subtype)){
return (kResponseBodyViewOptions[type]![subtype]!, kCodeHighlighterMap[subtype] ?? subtype);
if (kResponseBodyViewOptions.containsKey(type)) {
if (kResponseBodyViewOptions[type]!.containsKey(subtype)) {
return (
kResponseBodyViewOptions[type]![subtype]!,
kCodeHighlighterMap[subtype] ?? subtype
);
}
if(subtype.contains(kSubTypeJson)){
if (subtype.contains(kSubTypeJson)) {
subtype = kSubTypeJson;
}
if(subtype.contains(kSubTypeXml)){
if (subtype.contains(kSubTypeXml)) {
subtype = kSubTypeXml;
}
if (kResponseBodyViewOptions[type]!.containsKey(subtype)){
return (kResponseBodyViewOptions[type]![subtype]!, kCodeHighlighterMap[subtype] ?? subtype);
if (kResponseBodyViewOptions[type]!.containsKey(subtype)) {
return (
kResponseBodyViewOptions[type]![subtype]!,
kCodeHighlighterMap[subtype] ?? subtype
);
}
return (kResponseBodyViewOptions[type]![kSubTypeDefaultViewOptions]!, subtype);
return (
kResponseBodyViewOptions[type]![kSubTypeDefaultViewOptions]!,
subtype
);
}
}
return (kNoBodyViewOptions, null);
}
String? formatBody(String? body, MediaType? mediaType){
if(mediaType != null && body != null){
String? formatBody(String? body, MediaType? mediaType) {
if (mediaType != null && body != null) {
var subtype = mediaType.subtype;
try {
if(subtype.contains(kSubTypeJson)){
if (subtype.contains(kSubTypeJson)) {
final tmp = jsonDecode(body);
String result = kEncoder.convert(tmp);
return result;
}
if(subtype.contains(kSubTypeXml)){
if (subtype.contains(kSubTypeXml)) {
final document = XmlDocument.parse(body);
String result = document.toXmlString(pretty: true, indent: ' ');
return result;
}
if(subtype == kSubTypeHtml){
if (subtype == kSubTypeHtml) {
var len = body.length;
var lines = kSplitter.convert(body);
var numOfLines = lines.length;
if(numOfLines !=0 && len/numOfLines <= kCodeCharsPerLineLimit){
if (numOfLines != 0 && len / numOfLines <= kCodeCharsPerLineLimit) {
return body;
}
}
@ -143,4 +150,4 @@ String? formatBody(String? body, MediaType? mediaType){
}
}
return null;
}
}

View File

@ -2,3 +2,4 @@ export 'ui_utils.dart';
export 'convert_utils.dart';
export 'http_utils.dart';
export 'file_utils.dart';
export 'window_utils.dart';

View File

@ -0,0 +1,18 @@
bool showButtonLabelsInBodySuccess(int options, double maxWidth) {
switch (options) {
case 0:
return true;
case 1:
return (maxWidth < 300) ? false : true;
case 2:
return (maxWidth < 400) ? false : true;
case 3:
return (maxWidth < 500) ? false : true;
default:
return false;
}
}
bool showButtonLabelsInViewCodePane(double maxWidth) {
return (maxWidth < 400) ? false : true;
}

View File

@ -104,12 +104,14 @@ class SaveInDownloadsButton extends StatefulWidget {
super.key,
this.content,
this.mimeType,
this.ext,
this.name,
this.showLabel = true,
});
final Uint8List? content;
final String? mimeType;
final String? ext;
final String? name;
final bool showLabel;
@ -129,10 +131,9 @@ class _SaveInDownloadsButtonState extends State<SaveInDownloadsButton> {
onPressed: (widget.content != null)
? () async {
var message = "";
var ext = getFileExtension(widget.mimeType);
var path = await getFileDownloadpath(
widget.name,
ext,
widget.ext ?? getFileExtension(widget.mimeType),
);
if (path != null) {
try {

View File

@ -6,7 +6,7 @@ import 'error_message.dart';
(String, bool) sanitize(String input) {
bool limitedLines = false;
int tabSize = 4;
var lines = kSplitter.convert(input);
var lines = kSplitter.convert(input);
if (lines.length > kCodePreviewLinesLimit) {
lines = lines.sublist(0, kCodePreviewLinesLimit);
limitedLines = true;
@ -67,7 +67,12 @@ class _CodePreviewerState extends State<CodePreviewer> {
textStyle = textStyle.merge(widget.textStyle);
}
processed = sanitize(widget.code);
spans = asyncGenerateSpans(processed.$0, widget.language, widget.theme, processed.$1);
spans = asyncGenerateSpans(
processed.$1,
widget.language,
widget.theme,
processed.$2,
);
}
@override
@ -131,12 +136,14 @@ class _CodePreviewerState extends State<CodePreviewer> {
}
}
Future<List<TextSpan>> asyncGenerateSpans(
String code, String? language, Map<String, TextStyle> theme, bool limitedLines) async {
Future<List<TextSpan>> asyncGenerateSpans(String code, String? language,
Map<String, TextStyle> theme, bool limitedLines) async {
var parsed = highlight.parse(code, language: language);
var spans = convert(parsed.nodes!, theme);
if(limitedLines) {
spans.add(const TextSpan(text: "\n... more.\nPreview ends here ($kCodePreviewLinesLimit lines).\nYou can check Raw for full result."));
if (limitedLines) {
spans.add(const TextSpan(
text:
"\n... more.\nPreview ends here ($kCodePreviewLinesLimit lines).\nYou can check Raw for full result."));
}
return spans;
}

View File

@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:highlighter/highlighter.dart' show highlight;
import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart';
import 'code_previewer.dart' show convert;
import 'buttons.dart';
import 'code_previewer.dart';
import 'widgets.dart'
show CopyButton, DropdownButtonCodegenLanguage, SaveInDownloadsButton;
class CodeGenPreviewer extends StatefulWidget {
const CodeGenPreviewer({
@ -105,9 +106,13 @@ class ViewCodePane extends StatefulWidget {
const ViewCodePane({
super.key,
required this.code,
required this.codegenLanguage,
required this.onChangedCodegenLanguage,
});
final String code;
final CodegenLanguage codegenLanguage;
final Function(CodegenLanguage?) onChangedCodegenLanguage;
@override
State<ViewCodePane> createState() => _ViewCodePaneState();
@ -130,44 +135,55 @@ class _ViewCodePaneState extends State<ViewCodePane> {
borderRadius: kBorderRadius8,
);
return Padding(
padding: kP10,
child: Column(
children: [
SizedBox(
height: kHeaderHeight,
child: Row(
children: [
Expanded(
child: Text(
"Code",
style: Theme.of(context).textTheme.titleMedium,
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
var showLabel = showButtonLabelsInViewCodePane(
constraints.maxWidth,
);
return Padding(
padding: kP10,
child: Column(
children: [
SizedBox(
height: kHeaderHeight,
child: Row(
children: [
Expanded(
child: DropdownButtonCodegenLanguage(
codegenLanguage: widget.codegenLanguage,
onChanged: widget.onChangedCodegenLanguage,
),
),
CopyButton(
toCopy: widget.code,
showLabel: showLabel,
),
SaveInDownloadsButton(
content: stringToBytes(widget.code),
ext: widget.codegenLanguage.ext,
showLabel: showLabel,
)
],
),
),
kVSpacer10,
Expanded(
child: Container(
width: double.maxFinite,
padding: kP8,
decoration: textContainerdecoration,
child: CodeGenPreviewer(
code: widget.code,
theme: codeTheme,
language: widget.codegenLanguage.codeHighlightLang,
textStyle: kCodeStyle,
),
),
CopyButton(toCopy: widget.code),
SaveInDownloadsButton(
content: stringToBytes(widget.code),
mimeType: "application/vnd.dart",
)
],
),
),
kVSpacer10,
Expanded(
child: Container(
width: double.maxFinite,
padding: kP8,
decoration: textContainerdecoration,
child: CodeGenPreviewer(
code: widget.code,
theme: codeTheme,
language: 'dart',
textStyle: kCodeStyle,
),
),
],
),
],
),
);
},
);
}
}

View File

@ -109,3 +109,55 @@ class _DropdownButtonContentTypeState extends State<DropdownButtonContentType> {
);
}
}
class DropdownButtonCodegenLanguage extends StatefulWidget {
const DropdownButtonCodegenLanguage({
Key? key,
this.codegenLanguage,
this.onChanged,
}) : super(key: key);
@override
State<DropdownButtonCodegenLanguage> createState() =>
_DropdownButtonCodegenLanguageState();
final CodegenLanguage? codegenLanguage;
final void Function(CodegenLanguage?)? onChanged;
}
class _DropdownButtonCodegenLanguageState
extends State<DropdownButtonCodegenLanguage> {
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<CodegenLanguage>(
focusColor: surfaceColor,
value: widget.codegenLanguage,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: Container(
height: 0,
),
onChanged: widget.onChanged,
borderRadius: kBorderRadius12,
items: CodegenLanguage.values
.map<DropdownMenuItem<CodegenLanguage>>((CodegenLanguage value) {
return DropdownMenuItem<CodegenLanguage>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.label,
style: kTextStyleButton,
),
),
);
}).toList(),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:apidash/consts.dart';
import 'markdown.dart';
import 'error_message.dart';
@ -28,7 +29,10 @@ class _IntroMessageState extends State<IntroMessage> {
} else {
text = text.replaceAll("{{mode}}", "light");
}
return CustomMarkdown(data: text);
return CustomMarkdown(
data: text,
padding: kPh60,
);
}
if (snapshot.hasError) {
return const ErrorMessage(message: "An error occured");

View File

@ -8,8 +8,11 @@ class CustomMarkdown extends StatefulWidget {
const CustomMarkdown({
super.key,
required this.data,
this.padding = const EdgeInsets.all(16.0),
});
final String data;
final EdgeInsets padding;
@override
State<CustomMarkdown> createState() => _CustomMarkdownState();
}
@ -22,6 +25,7 @@ class _CustomMarkdownState extends State<CustomMarkdown> {
p: Theme.of(context).textTheme.titleMedium,
);
return Markdown(
padding: widget.padding,
styleSheet: mdStyleSheet,
data: widget.data,
selectable: true,

View File

@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import 'error_message.dart';
import 'package:apidash/consts.dart';
import 'package:printing/printing.dart';
import 'uint8_audio_player.dart';
class Previewer extends StatefulWidget {
const Previewer({
@ -43,7 +45,14 @@ class _PreviewerState extends State<Previewer> {
);
}
if (widget.type == kTypeAudio) {
// TODO: Audio Player
return Uint8AudioPlayer(
bytes: widget.bytes,
type: widget.type!,
subtype: widget.subtype!,
errorBuilder: (context, error, stacktrace) {
return const ErrorMessage(message: kAudioError);
},
);
}
if (widget.type == kTypeVideo) {
// TODO: Video Player

View File

@ -339,8 +339,8 @@ class _ResponseBodyState extends State<ResponseBody> {
}
var responseBodyView = getResponseBodyViewOptions(mediaType);
var options = responseBodyView.$0;
var highlightLanguage = responseBodyView.$1;
var options = responseBodyView.$1;
var highlightLanguage = responseBodyView.$2;
if (formattedBody == null) {
options = [...options];
@ -400,18 +400,10 @@ class _BodySuccessState extends State<BodySuccess> {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
var showLabel = false;
switch (widget.options.length) {
case 1:
showLabel = (constraints.maxWidth < 300) ? false : true;
break;
case 2:
showLabel = (constraints.maxWidth < 400) ? false : true;
break;
case 3:
showLabel = (constraints.maxWidth < 500) ? false : true;
break;
}
var showLabel = showButtonLabelsInBodySuccess(
widget.options.length,
constraints.maxWidth,
);
return Padding(
padding: kP10,
child: Column(

View File

@ -76,8 +76,8 @@ class EqualSplitView extends StatefulWidget {
class _EqualSplitViewState extends State<EqualSplitView> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(minimalSize: 300),
Area(minimalSize: 300),
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
],
);

View File

@ -0,0 +1,240 @@
import 'dart:typed_data';
import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
typedef AudioErrorWidgetBuilder = Widget Function(
BuildContext context,
Object error,
StackTrace? stackTrace,
);
// Uint8List AudioSource for just_audio
class Uint8AudioSource extends StreamAudioSource {
Uint8AudioSource(this.bytes, {required this.contentType});
final List<int> bytes;
final String contentType;
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
start ??= 0;
end ??= bytes.length;
return StreamAudioResponse(
sourceLength: bytes.length,
contentLength: end - start,
offset: start,
stream: Stream.value(bytes.sublist(start, end)),
contentType: contentType,
);
}
}
class Uint8AudioPlayer extends StatefulWidget {
/// Creates a widget for playing audio obtained from a [Uint8List].
const Uint8AudioPlayer({
super.key,
required this.bytes,
required this.type,
required this.subtype,
required this.errorBuilder,
});
final Uint8List bytes;
final String type;
final String subtype;
final AudioErrorWidgetBuilder errorBuilder;
@override
State<Uint8AudioPlayer> createState() => _Uint8AudioPlayerState();
}
class _Uint8AudioPlayerState extends State<Uint8AudioPlayer> {
final player = AudioPlayer();
@override
void initState() {
player.setAudioSource(Uint8AudioSource(
widget.bytes,
contentType: '${widget.type}/${widget.subtype}',
));
super.initState();
}
@override
void dispose() {
player.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<PlayerState>(
stream: player.playerStateStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return widget.errorBuilder(
context, snapshot.error!, snapshot.stackTrace);
} else {
final playerState = snapshot.data;
final processingState = playerState?.processingState;
if (processingState == ProcessingState.ready ||
processingState == ProcessingState.completed ||
processingState == ProcessingState.buffering) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Audio Player
Padding(
padding: kPh20v10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Duration Position Builder (time elapsed)
_buildDuration(
player.positionStream,
maxDuration: player.duration,
),
// Slider to view & change Duration Position
_buildPositionBar(
player.positionStream,
maxDuration: player.duration,
onChanged: (value) =>
player.seek(Duration(seconds: value.toInt())),
),
// Total Duration
Text(
audioPosition(player.duration),
style: TextStyle(fontFamily: kCodeStyle.fontFamily),
),
],
),
),
// Audio Player Controls
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Play/Pause Button
_buildPlayButton(
player.playingStream,
play: player.play,
pause: player.pause,
restart: () => player.seek(Duration.zero),
completed: processingState == ProcessingState.completed,
),
// Mute/UnMute button
_buildVolumeButton(
player.volumeStream,
mute: () => player.setVolume(0),
unmute: () => player.setVolume(1),
),
],
),
],
);
} else if (processingState == ProcessingState.idle) {
// Error in Loading AudioSource
return widget.errorBuilder(
context,
ErrorDescription('${player.audioSource} Loading Error'),
snapshot.stackTrace,
);
} else {
return const Center(child: CircularProgressIndicator());
}
}
},
);
}
StreamBuilder<bool> _buildPlayButton(
Stream<bool> stream, {
VoidCallback? play,
VoidCallback? pause,
VoidCallback? restart,
required bool completed,
}) {
return StreamBuilder<bool>(
stream: stream,
builder: (context, snapshot) {
final playing = snapshot.data;
if (playing != true) {
return IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: play,
);
} else if (completed) {
return IconButton(
icon: const Icon(Icons.replay),
onPressed: restart,
);
} else {
return IconButton(
icon: const Icon(Icons.pause),
onPressed: pause,
);
}
},
);
}
StreamBuilder<Duration> _buildDuration(
Stream<Duration> stream, {
Duration? maxDuration,
}) {
return StreamBuilder<Duration>(
stream: stream,
builder: (context, snapshot) {
final position = snapshot.data;
return Text(
audioPosition(position),
style: TextStyle(fontFamily: kCodeStyle.fontFamily),
);
},
);
}
StreamBuilder<Duration> _buildPositionBar(
Stream<Duration> stream, {
Duration? maxDuration,
ValueChanged<double>? onChanged,
}) {
return StreamBuilder<Duration>(
stream: stream,
builder: (context, snapshot) {
return Flexible(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackShape: const RectangularSliderTrackShape(),
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 16.0),
),
child: Slider(
value: snapshot.data?.inSeconds.toDouble() ?? 0,
max: maxDuration?.inSeconds.toDouble() ?? 0,
onChanged: onChanged,
),
),
);
},
);
}
StreamBuilder<double> _buildVolumeButton(Stream<double> stream,
{VoidCallback? mute, VoidCallback? unmute}) {
return StreamBuilder<double>(
stream: stream,
builder: (context, snapshot) {
return snapshot.data == 0
? IconButton(icon: const Icon(Icons.volume_off), onPressed: unmute)
: IconButton(icon: const Icon(Icons.volume_up), onPressed: mute);
},
);
}
}

View File

@ -16,3 +16,4 @@ export 'request_widgets.dart';
export 'response_widgets.dart';
export 'snackbars.dart';
export 'markdown.dart';
export 'uint8_audio_player.dart';

File diff suppressed because it is too large Load Diff

View File

@ -4,43 +4,52 @@ publish_to: 'none'
version: 0.2.0+2
environment:
sdk: '>=2.19.2 <3.0.0'
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.7.2'
dependencies:
flutter:
sdk: flutter
multi_split_view: ^2.4.0
url_launcher: ^6.1.10
flutter_riverpod: ^2.1.3
url_launcher: ^6.1.12
flutter_riverpod: ^2.3.7
uuid: ^3.0.7
davi: ^3.2.0
http: ^0.13.5
davi: ^3.4.1
http: ^1.1.0
http_parser: ^4.0.2
collection: ^1.17.0
google_fonts: ^4.0.3
collection: ^1.17.2
google_fonts: ^5.1.0
highlighter: ^0.1.1
xml: ^6.2.2
jinja: ^0.4.2
xml: ^6.3.0
jinja: ^0.5.0
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size
hive_flutter: ^1.1.0
lottie: ^2.3.2
lottie: ^2.6.0
mime_dart: ^3.0.0
path_provider: ^2.0.14
window_manager: ^0.3.2
path: ^1.8.2
flutter_markdown: ^0.6.14
markdown: ^7.1.0
printing: ^5.10.4
path_provider: ^2.1.0
window_manager: ^0.3.5
path: ^1.8.3
flutter_markdown: ^0.6.17+1
markdown: ^7.1.1
just_audio: ^0.9.34
just_audio_mpv: ^0.1.7
just_audio_windows: ^0.2.0
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
printing: ^5.11.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter_launcher_icons: ^0.12.0
test: ^1.22.0
flutter_lints: ^2.0.2
flutter_launcher_icons: ^0.13.1
test: ^1.24.3
build_runner: ^2.4.6
freezed: ^2.4.1
json_serializable: ^6.7.1
flutter:
uses-material-design: true

View File

@ -0,0 +1,505 @@
import 'package:apidash/codegen/dart/pkg_http.dart';
import 'package:test/test.dart';
import '../request_models.dart';
void main() {
final dartHttpCodeGen = DartHttpCodeGen();
group('GET Request', () {
test('GET 1', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com');
final response = await http.get(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet1, "https"), expectedCode);
});
test('GET 2', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com/country/data');
var queryParams = {
"code": "US"
};
uri = uri.replace(queryParameters: queryParams);
final response = await http.get(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet2, "https"), expectedCode);
});
test('GET 3', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com/country/data?code=US');
var queryParams = {
"code": "IND"
};
var urlQueryParams = Map<String,String>.from(uri.queryParameters);
urlQueryParams.addAll(queryParams);
uri = uri.replace(queryParameters: urlQueryParams);
final response = await http.get(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet3, "https"), expectedCode);
});
test('GET 4', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com/humanize/social');
var queryParams = {
"num": "8700000",
"digits": "3",
"system": "SS",
"add_space": "true",
"trailing_zeros": "true"
};
uri = uri.replace(queryParameters: queryParams);
final response = await http.get(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet4, "https"), expectedCode);
});
test('GET 5', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.github.com/repos/foss42/apidash');
var headers = {
"Authorization": "Bearer XYZ"
};
final response = await http.get(uri,
headers: headers);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet5, "https"), expectedCode);
});
test('GET 6', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.github.com/repos/foss42/apidash');
var queryParams = {
"raw": "true"
};
uri = uri.replace(queryParameters: queryParams);
var headers = {
"Authorization": "Bearer XYZ"
};
final response = await http.get(uri,
headers: headers);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet6, "https"), expectedCode);
});
test('GET 7', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com');
final response = await http.get(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet7, "https"), expectedCode);
});
test('GET 8', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.github.com/repos/foss42/apidash');
var queryParams = {
"raw": "true"
};
uri = uri.replace(queryParameters: queryParams);
var headers = {
"Authorization": "Bearer XYZ"
};
final response = await http.get(uri,
headers: headers);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelGet8, "https"), expectedCode);
});
});
group('HEAD Request', () {
test('HEAD 1', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com');
final response = await http.head(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelHead1, "https"), expectedCode);
});
test('HEAD 2', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('http://api.foss42.com');
final response = await http.head(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelHead2, "http"), expectedCode);
});
});
group('POST Request', () {
test('POST 1', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com/case/lower');
String body = r'''{
"text": "I LOVE Flutter"
}''';
var headers = {
"content-type": "text/plain"
};
final response = await http.post(uri,
headers: headers,
body: body);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelPost1, "https"), expectedCode);
});
test('POST 2', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com/case/lower');
String body = r'''{
"text": "I LOVE Flutter"
}''';
var headers = {
"content-type": "application/json"
};
final response = await http.post(uri,
headers: headers,
body: body);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelPost2, "https"), expectedCode);
});
test('POST 3', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://api.foss42.com/case/lower');
String body = r'''{
"text": "I LOVE Flutter"
}''';
var headers = {
"Authorization": "Bearer XYZ",
"content-type": "application/json"
};
final response = await http.post(uri,
headers: headers,
body: body);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelPost3, "https"), expectedCode);
});
});
group('PUT Request', () {
test('PUT 1', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://reqres.in/api/users/2');
String body = r'''{
"name": "morpheus",
"job": "zion resident"
}''';
var headers = {
"content-type": "application/json"
};
final response = await http.put(uri,
headers: headers,
body: body);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(dartHttpCodeGen.getCode(requestModelPut1, "https"), expectedCode);
});
});
group('PATCH Request', () {
test('PATCH 1', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://reqres.in/api/users/2');
String body = r'''{
"name": "marfeus",
"job": "accountant"
}''';
var headers = {
"content-type": "application/json"
};
final response = await http.patch(uri,
headers: headers,
body: body);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(
dartHttpCodeGen.getCode(requestModelPatch1, "https"), expectedCode);
});
});
group('DELETE Request', () {
test('DELETE 1', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://reqres.in/api/users/2');
final response = await http.delete(uri);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(
dartHttpCodeGen.getCode(requestModelDelete1, "https"), expectedCode);
});
test('DELETE 2', () {
const expectedCode = r"""import 'package:http/http.dart' as http;
void main() async {
var uri = Uri.parse('https://reqres.in/api/users/2');
String body = r'''{
"name": "marfeus",
"job": "accountant"
}''';
var headers = {
"content-type": "application/json"
};
final response = await http.delete(uri,
headers: headers,
body: body);
int statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 300) {
print('Status Code: $statusCode');
print('Response Body: ${response.body}');
}
else{
print('Error Status Code: $statusCode');
print('Error Response Body: ${response.body}');
}
}
""";
expect(
dartHttpCodeGen.getCode(requestModelDelete2, "https"), expectedCode);
});
});
}

View File

@ -0,0 +1,126 @@
import 'package:apidash/codegen/kotlin/pkg_okhttp.dart';
import 'package:test/test.dart';
import '../request_models.dart';
void main() {
group('KotlinOkHttpCodeGen', () {
final kotlinOkHttpCodeGen = KotlinOkHttpCodeGen();
test('getCode returns valid code for GET request', () {
const expectedCode = r"""import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.foss42.com")
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModelGet1), expectedCode);
});
test('getCode returns valid code for POST request', () {
const expectedCode = r"""import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{"text": "IS Upper"}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://api.foss42.com/case/lower")
.post(body)
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModelPost1), expectedCode);
});
test('getCode returns valid code for DELETE request', () {
const expectedCode = r"""import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val mediaType = "application/json".toMediaType()
val body = "{"title": "foo","body": "bar","userId": 1}".toRequestBody(mediaType)
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts/1")
.method("DELETE", body)
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModelDelete1), expectedCode);
});
test('getCode returns valid code for HEAD request', () {
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts/1")
.head()
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModelHead1), expectedCode);
});
test(
'getCode returns valid code for requests with headers and query parameters',
() {
const expectedCode = """import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.concurrent.TimeUnit
val client = OkHttpClient()
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts")
.addQueryParameter("userId", "1")
.addHeader("Custom-Header-1", "Value-1")
.addHeader("Custom-Header-2", "Value-2")
.build()
val response = client.newCall(request).execute()
println(response.body!!.string())
""";
expect(kotlinOkHttpCodeGen.getCode(requestModelGet2), expectedCode);
});
});
}

View File

@ -0,0 +1,135 @@
import 'package:apidash/codegen/python/pkg_http_client.dart';
import 'package:apidash/models/models.dart';
import 'package:test/test.dart';
import 'package:apidash/consts.dart';
void main() {
group('PythonHttpClient', () {
final PythonHttpClient pythonHttpClient = PythonHttpClient();
test('getCode returns valid code for GET request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: HTTPVerb.get,
id: '',
);
const expectedCode = """import http.client
import json
conn = http.client.HTTPSConnection('jsonplaceholder.typicode.com')
payload = json.dumps()
headers = {
'Content-Type':'application/json'
},
conn.request("GET", "/todos/1", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
""";
expect(pythonHttpClient.getCode(requestModel), expectedCode);
});
test('getCode returns valid code for POST request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/todos',
method: HTTPVerb.post,
requestBody: '{"title": "foo","body": "bar","userId": 1}',
requestBodyContentType: ContentType.json,
id: '1',
);
const expectedCode = """import http.client
import json
conn = http.client.HTTPSConnection('jsonplaceholder.typicode.com')
payload = json.dumps({"title": "foo","body": "bar","userId": 1})
headers = {
'Content-Type':'application/json'
},
conn.request("POST", "/todos", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
""";
expect(pythonHttpClient.getCode(requestModel), expectedCode);
});
test('getCode returns valid code for DELETE request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: HTTPVerb.delete,
requestBody: '{"title": "foo","body": "bar","userId": 1}',
requestBodyContentType: ContentType.json,
id: '1',
);
const expectedCode = """import http.client
import json
conn = http.client.HTTPSConnection('jsonplaceholder.typicode.com')
payload = json.dumps({"title": "foo","body": "bar","userId": 1})
headers = {
'Content-Type':'application/json'
},
conn.request("DELETE", "/todos/1", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
""";
expect(pythonHttpClient.getCode(requestModel), expectedCode);
});
test('getCode returns valid code for HEAD request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: HTTPVerb.head,
id: '1',
);
const expectedCode = """import http.client
import json
conn = http.client.HTTPSConnection('jsonplaceholder.typicode.com')
payload = json.dumps()
headers = {
'Content-Type':'application/json'
},
conn.request("HEAD", "/todos/1", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
""";
expect(pythonHttpClient.getCode(requestModel), expectedCode);
});
test(
'getCode returns valid code for requests with headers and query parameters',
() {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts',
method: HTTPVerb.get,
requestParams: [
NameValueModel(name: 'userId', value: 1),
],
requestHeaders: [
NameValueModel(name: 'Custom-Header-1', value: 'Value-1'),
NameValueModel(name: 'Custom-Header-2', value: 'Value-2')
],
id: '1',
);
const expectedCode = """import http.client
import json
conn = http.client.HTTPSConnection('jsonplaceholder.typicode.com')
payload = json.dumps()
headers = {
'Content-Type':'application/json'
'Custom-Header-1':'Value-1',
'Custom-Header-2':'Value-2',
},
conn.request("GET", "/posts?userId=1", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
""";
expect(pythonHttpClient.getCode(requestModel), expectedCode);
});
});
}

View File

@ -0,0 +1,179 @@
import 'package:apidash/codegen/python/pkg_request.dart';
import 'package:apidash/models/models.dart';
import 'package:test/test.dart';
import 'package:apidash/consts.dart';
void main() {
group('PythonRequestCodeGen', () {
final pythonRequestCodeGen = PythonRequestCodeGen();
test('getCode returns valid code for GET request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/todos/1',
method: HTTPVerb.get,
id: '',
);
const expectedCode = """import requests
def main():
url = 'https://jsonplaceholder.typicode.com/todos/1'
response = requests.get(
url
)
status_code = response.status_code
if 200 <= status_code < 300:
print('Status Code:', status_code)
print('Response Body:', response.text)
else:
print('Error Status Code:', status_code)
print('Error Response Body:', response.reason)
main()""";
expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode);
});
test('getCode returns valid code for POST request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts',
method: HTTPVerb.post,
requestBody: '{"title": "foo","body": "bar","userId": 1}',
requestBodyContentType: ContentType.json,
id: '1',
);
const expectedCode = """import requests
def main():
url = 'https://jsonplaceholder.typicode.com/posts'
data = '''{"title": "foo","body": "bar","userId": 1}'''
headers = {
"content-type": "application/json"
}
response = requests.post(
url, headers=headers, data=data
)
status_code = response.status_code
if 200 <= status_code < 300:
print('Status Code:', status_code)
print('Response Body:', response.text)
else:
print('Error Status Code:', status_code)
print('Error Response Body:', response.reason)
main()""";
expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode);
});
test('getCode returns valid code for DELETE request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: HTTPVerb.delete,
requestBody: '{"title": "foo","body": "bar","userId": 1}',
requestBodyContentType: ContentType.json,
id: '1',
);
const expectedCode = """import requests
def main():
url = 'https://jsonplaceholder.typicode.com/posts/1'
data = '''{"title": "foo","body": "bar","userId": 1}'''
headers = {
"content-type": "application/json"
}
response = requests.delete(
url, headers=headers, data=data
)
status_code = response.status_code
if 200 <= status_code < 300:
print('Status Code:', status_code)
print('Response Body:', response.text)
else:
print('Error Status Code:', status_code)
print('Error Response Body:', response.reason)
main()""";
expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode);
});
test('getCode returns valid code for HEAD request', () {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: HTTPVerb.head,
id: '1',
);
const expectedCode = """import requests
def main():
url = 'https://jsonplaceholder.typicode.com/posts/1'
response = requests.head(
url
)
status_code = response.status_code
if 200 <= status_code < 300:
print('Status Code:', status_code)
print('Response Body:', response.text)
else:
print('Error Status Code:', status_code)
print('Error Response Body:', response.reason)
main()""";
expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode);
});
test(
'getCode returns valid code for requests with headers and query parameters',
() {
const requestModel = RequestModel(
url: 'https://jsonplaceholder.typicode.com/posts',
method: HTTPVerb.get,
requestParams: [
NameValueModel(name: 'userId', value: 1),
],
requestHeaders: [
NameValueModel(name: 'Custom-Header-1', value: 'Value-1'),
NameValueModel(name: 'Custom-Header-2', value: 'Value-2')
],
id: '1',
);
const expectedCode = """import requests
def main():
url = 'https://jsonplaceholder.typicode.com/posts'
params = {
"userId": "1",
}
headers = {
"Custom-Header-1": "Value-1",
"Custom-Header-2": "Value-2"
}
response = requests.get(
url, params=params, headers=headers
)
status_code = response.status_code
if 200 <= status_code < 300:
print('Status Code:', status_code)
print('Response Body:', response.text)
else:
print('Error Status Code:', status_code)
print('Error Response Body:', response.reason)
main()""";
expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode);
});
});
}

View File

@ -1,17 +1,17 @@
import 'package:test/test.dart';
import 'package:apidash/models/kvrow_model.dart';
import 'package:apidash/models/name_value_model.dart';
void main() {
KVRow kvRow1 = const KVRow("harry", 23);
const kvRow1 = NameValueModel(name: "harry", value: 23);
String kvRow1Expected = "{harry: 23}";
test('Testing toString()', () {
expect(kvRow1.toString(), kvRow1Expected);
});
KVRow kvRow2Expected = const KVRow("winter", "26");
const kvRow2Expected = NameValueModel(name: "winter", value: "26");
test('Testing copyWith()', () {
expect(kvRow1.copyWith(k: "winter", v: "26"), kvRow2Expected);
expect(kvRow1.copyWith(name: "winter", value: "26"), kvRow2Expected);
});
test('Testing hashcode', () {

View File

@ -55,8 +55,9 @@ void main() {
url: 'api.foss42.com/case/lower',
name: 'foss42 api',
requestHeaders: const [
KVRow('content-length', '18'),
KVRow('content-type', 'application/json; charset=utf-8')
NameValueModel(name: 'content-length', value: '18'),
NameValueModel(
name: 'content-type', value: 'application/json; charset=utf-8')
],
requestBodyContentType: ContentType.json,
requestBody: '''{
@ -71,8 +72,9 @@ void main() {
url: 'api.foss42.com/case/lower',
name: 'foss42 api',
requestHeaders: [
KVRow('content-length', '18'),
KVRow('content-type', 'application/json; charset=utf-8')
NameValueModel(name: 'content-length', value: '18'),
NameValueModel(
name: 'content-type', value: 'application/json; charset=utf-8')
],
requestBodyContentType: ContentType.json,
requestBody: '''{

182
test/request_models.dart Normal file
View File

@ -0,0 +1,182 @@
import 'package:apidash/models/models.dart' show NameValueModel, RequestModel;
import 'package:apidash/consts.dart';
/// Basic GET request model
const requestModelGet1 = RequestModel(
id: 'get1',
url: 'https://api.foss42.com',
method: HTTPVerb.get,
);
/// GET request model with query params
const requestModelGet2 = RequestModel(
id: 'get2',
url: 'https://api.foss42.com/country/data',
method: HTTPVerb.get,
requestParams: [
NameValueModel(name: 'code', value: 'US'),
],
);
/// GET request model with override query params
const requestModelGet3 = RequestModel(
id: 'get3',
url: 'https://api.foss42.com/country/data?code=US',
method: HTTPVerb.get,
requestParams: [
NameValueModel(name: 'code', value: 'IND'),
],
);
/// GET request model with different types of query params
const requestModelGet4 = RequestModel(
id: 'get4',
url: 'https://api.foss42.com/humanize/social',
method: HTTPVerb.get,
requestParams: [
NameValueModel(name: 'num', value: '8700000'),
NameValueModel(name: 'digits', value: '3'),
NameValueModel(name: 'system', value: 'SS'),
NameValueModel(name: 'add_space', value: 'true'),
NameValueModel(name: 'trailing_zeros', value: 'true'),
],
);
/// GET request model with headers
const requestModelGet5 = RequestModel(
id: 'get5',
url: 'https://api.github.com/repos/foss42/apidash',
method: HTTPVerb.get,
requestHeaders: [
NameValueModel(name: 'Authorization', value: 'Bearer XYZ'),
],
);
/// GET request model with headers & query params
const requestModelGet6 = RequestModel(
id: 'get6',
url: 'https://api.github.com/repos/foss42/apidash',
method: HTTPVerb.get,
requestHeaders: [
NameValueModel(name: 'Authorization', value: 'Bearer XYZ'),
],
requestParams: [
NameValueModel(name: 'raw', value: 'true'),
],
);
/// GET request model with body
const requestModelGet7 = RequestModel(
id: 'get7',
url: 'https://api.foss42.com',
method: HTTPVerb.get,
requestBodyContentType: ContentType.text,
requestBody:
'This is a random text which should not be attached with a GET request',
);
/// GET request model with empty header & query param name
const requestModelGet8 = RequestModel(
id: 'get8',
url: 'https://api.github.com/repos/foss42/apidash',
method: HTTPVerb.get,
requestHeaders: [
NameValueModel(name: 'Authorization', value: 'Bearer XYZ'),
NameValueModel(name: '', value: 'Bearer XYZ'),
],
requestParams: [
NameValueModel(name: 'raw', value: 'true'),
NameValueModel(name: '', value: 'true'),
],
);
/// Basic HEAD request model
const requestModelHead1 = RequestModel(
id: 'head1',
url: 'https://api.foss42.com',
method: HTTPVerb.head,
);
/// Without URI Scheme (pass default as http)
const requestModelHead2 = RequestModel(
id: 'head2',
url: 'api.foss42.com',
method: HTTPVerb.head,
);
/// Basic POST request model (txt body)
const requestModelPost1 = RequestModel(
id: 'post1',
url: 'https://api.foss42.com/case/lower',
method: HTTPVerb.post,
requestBody: r"""{
"text": "I LOVE Flutter"
}""",
requestBodyContentType: ContentType.text);
/// POST request model with JSON body
const requestModelPost2 = RequestModel(
id: 'post2',
url: 'https://api.foss42.com/case/lower',
method: HTTPVerb.post,
requestBody: r"""{
"text": "I LOVE Flutter"
}""",
);
/// POST request model with headers
const requestModelPost3 = RequestModel(
id: 'post3',
url: 'https://api.foss42.com/case/lower',
method: HTTPVerb.post,
requestBody: r"""{
"text": "I LOVE Flutter"
}""",
requestBodyContentType: ContentType.json,
requestHeaders: [
NameValueModel(name: 'Authorization', value: 'Bearer XYZ'),
],
);
/// PUT request model
const requestModelPut1 = RequestModel(
id: 'put1',
url: 'https://reqres.in/api/users/2',
method: HTTPVerb.put,
requestBody: r"""{
"name": "morpheus",
"job": "zion resident"
}""",
requestBodyContentType: ContentType.json,
);
/// PATCH request model
const requestModelPatch1 = RequestModel(
id: 'patch1',
url: 'https://reqres.in/api/users/2',
method: HTTPVerb.patch,
requestBody: r"""{
"name": "marfeus",
"job": "accountant"
}""",
requestBodyContentType: ContentType.json,
);
/// Basic DELETE request model
const requestModelDelete1 = RequestModel(
id: 'delete1',
url: 'https://reqres.in/api/users/2',
method: HTTPVerb.delete,
);
/// Basic DELETE with body
const requestModelDelete2 = RequestModel(
id: 'delete1',
url: 'https://reqres.in/api/users/2',
method: HTTPVerb.delete,
requestBody: r"""{
"name": "marfeus",
"job": "accountant"
}""",
requestBodyContentType: ContentType.json,
);

View File

@ -1,15 +1,19 @@
import 'dart:math';
const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
Random _rnd = Random();
class RandomStringGenerator {
static const _chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
static Random rnd = Random();
String getRandomString(int length) => String.fromCharCodes(Iterable.generate(
length, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length))));
static String getRandomString(int length) =>
String.fromCharCodes(Iterable.generate(
length, (_) => _chars.codeUnitAt(rnd.nextInt(_chars.length))));
String getRandomStringLines(int lines, int length) {
List<String> result = [];
for (var i = 0; i < lines; i++) {
result.add(getRandomString(length));
static String getRandomStringLines(int lines, int length) {
List<String> result = [];
for (var i = 0; i < lines; i++) {
result.add(getRandomString(length));
}
return result.join('\n');
}
return result.join('\n');
}

View File

@ -1,6 +1,6 @@
import 'package:test/test.dart';
import 'package:apidash/utils/convert_utils.dart';
import 'package:apidash/models/kvrow_model.dart';
import 'package:apidash/models/name_value_model.dart';
void main() {
group("Testing humanizeDuration function", () {
@ -73,18 +73,18 @@ void main() {
expect(rowsToMap(null), null);
});
test('Testing for string KVRow values', () {
KVRow kvRow1 = const KVRow("code", "IN");
const kvRow1 = NameValueModel(name: "code", value: "IN");
expect(rowsToMap([kvRow1]), {"code": "IN"});
});
test('Testing when header is True', () {
KVRow kvRow2 = const KVRow("Text", "ABC");
const kvRow2 = NameValueModel(name: "Text", value: "ABC");
expect(rowsToMap([kvRow2], isHeader: true), {"text": "ABC"});
});
test('Testing when header is false and key is in upper case', () {
List<KVRow> kvRow3 = const [
KVRow("TEXT", "ABC"),
KVRow("version", 0.1),
KVRow("month", 4)
const kvRow3 = <NameValueModel>[
NameValueModel(name: "TEXT", value: "ABC"),
NameValueModel(name: "version", value: 0.1),
NameValueModel(name: "month", value: 4),
];
expect(
rowsToMap(kvRow3), {"TEXT": "ABC", "version": "0.1", "month": "4"});
@ -97,10 +97,10 @@ void main() {
});
test('Testing with a map value', () {
Map<String, String> value1 = {"text": "abc", "lang": "eng", "code": "1"};
List<KVRow> result1Expected = const [
KVRow("text", "abc"),
KVRow("lang", "eng"),
KVRow("code", "1")
const result1Expected = <NameValueModel>[
NameValueModel(name: "text", value: "abc"),
NameValueModel(name: "lang", value: "eng"),
NameValueModel(name: "code", value: "1")
];
expect(mapToRows(value1), result1Expected);
});

View File

@ -0,0 +1,17 @@
import 'package:test/test.dart';
import 'package:apidash/utils/file_utils.dart';
void main() {
group(
"Testing x function",
() {
/*test('Test case 2', () {
expect(showButtonLabelsInViewCodePane(350), false);
});
test('Test case 3', () {
expect(showButtonLabelsInViewCodePane(450), true);
});*/
},
);
}

View File

@ -1,7 +1,7 @@
import 'package:test/test.dart';
import 'package:http_parser/http_parser.dart';
import 'package:apidash/utils/http_utils.dart';
import 'package:apidash/models/kvrow_model.dart';
import 'package:apidash/models/name_value_model.dart';
import 'package:apidash/consts.dart';
import '../test_utilities.dart';
@ -156,74 +156,74 @@ void main() {
path: 'guides/libraries/library-tour',
fragment: 'numbers');
String uriScheme1Expected = 'https';
expect(getUriScheme(uri1), (uriScheme1Expected,true));
expect(getUriScheme(uri1), (uriScheme1Expected, true));
});
test('Testing getUriScheme for mailto scheme value', () {
Uri uri2 = Uri(scheme: 'mailto');
String uriScheme2Expected = 'mailto';
expect(getUriScheme(uri2), (uriScheme2Expected,false));
expect(getUriScheme(uri2), (uriScheme2Expected, false));
});
test('Testing getUriScheme for empty scheme value', () {
Uri uri3 = Uri(
scheme: '');
expect(getUriScheme(uri3), (null,false));
Uri uri3 = Uri(scheme: '');
expect(getUriScheme(uri3), (null, false));
});
test('Testing getUriScheme for null scheme value', () {
Uri uri4 = Uri(
scheme: null);
expect(getUriScheme(uri4), (null,false));
Uri uri4 = Uri(scheme: null);
expect(getUriScheme(uri4), (null, false));
});
});
group("Testing getValidRequestUri", () {
test('Testing getValidRequestUri for normal values', () {
String url1 = "https://api.foss42.com/country/data";
KVRow kvRow1 = const KVRow("code", "US");
const kvRow1 = NameValueModel(name: "code", value: "US");
Uri uri1Expected = Uri(
scheme: 'https',
host: 'api.foss42.com',
path: 'country/data',
queryParameters: {'code':'US'});
queryParameters: {'code': 'US'});
expect(getValidRequestUri(url1, [kvRow1]), (uri1Expected, null));
});
test('Testing getValidRequestUri for null url value', () {
KVRow kvRow2 = const KVRow("code", "US");
const kvRow2 = NameValueModel(name: "code", value: "US");
expect(getValidRequestUri(null, [kvRow2]), (null, "URL is missing!"));
});
test('Testing getValidRequestUri for empty url value', () {
KVRow kvRow3 = const KVRow("", "");
const kvRow3 = NameValueModel(name: "", value: "");
expect(getValidRequestUri("", [kvRow3]), (null, "URL is missing!"));
});
test('Testing getValidRequestUri when https is not provided in url', () {
String url4 = "api.foss42.com/country/data";
KVRow kvRow4 = const KVRow("code", "US");
const kvRow4 = NameValueModel(name: "code", value: "US");
Uri uri4Expected = Uri(
scheme: 'https',
host: 'api.foss42.com',
path: 'country/data',
queryParameters: {'code':'US'});
queryParameters: {'code': 'US'});
expect(getValidRequestUri(url4, [kvRow4]), (uri4Expected, null));
});
test('Testing getValidRequestUri when url has fragment', () {
String url5 = "https://dart.dev/guides/libraries/library-tour#numbers";
Uri uri5Expected = Uri(
scheme: 'https',
host: 'dart.dev',
path: '/guides/libraries/library-tour');
scheme: 'https',
host: 'dart.dev',
path: '/guides/libraries/library-tour');
expect(getValidRequestUri(url5, null), (uri5Expected, null));
});
test('Testing getValidRequestUri when uri scheme is not supported', () {
String url5 = "mailto:someone@example.com";
expect(getValidRequestUri(url5, null), (null, "Unsupported URL Scheme (mailto)"));
expect(getValidRequestUri(url5, null),
(null, "Unsupported URL Scheme (mailto)"));
});
test('Testing getValidRequestUri when query params in both url and kvrow', () {
test('Testing getValidRequestUri when query params in both url and kvrow',
() {
String url6 = "api.foss42.com/country/data?code=IND";
KVRow kvRow6 = const KVRow("code", "US");
const kvRow6 = NameValueModel(name: "code", value: "US");
Uri uri6Expected = Uri(
scheme: 'https',
host: 'api.foss42.com',
path: 'country/data',
queryParameters: {'code':'US'});
queryParameters: {'code': 'US'});
expect(getValidRequestUri(url6, [kvRow6]), (uri6Expected, null));
});
test('Testing getValidRequestUri when kvrow is null', () {
@ -232,7 +232,7 @@ void main() {
scheme: 'https',
host: 'api.foss42.com',
path: 'country/data',
queryParameters: {'code':'US'});
queryParameters: {'code': 'US'});
expect(getValidRequestUri(url7, null), (uri7Expected, null));
});
});
@ -241,72 +241,78 @@ void main() {
test('Testing getResponseBodyViewOptions for application/json', () {
MediaType mediaType1 = MediaType("application", "json");
var result1 = getResponseBodyViewOptions(mediaType1);
expect(result1.$0,kCodeRawBodyViewOptions);
expect(result1.$1, "json");
expect(result1.$1, kCodeRawBodyViewOptions);
expect(result1.$2, "json");
});
test('Testing getResponseBodyViewOptions for application/xml', () {
MediaType mediaType2 = MediaType("application", "xml");
var result2 = getResponseBodyViewOptions(mediaType2);
expect(result2.$0, kCodeRawBodyViewOptions);
expect(result2.$1,"xml");
expect(result2.$1, kCodeRawBodyViewOptions);
expect(result2.$2, "xml");
});
test('Testing getResponseBodyViewOptions for message/news a format currently not supported', () {
test(
'Testing getResponseBodyViewOptions for message/news a format currently not supported',
() {
MediaType mediaType3 = MediaType("message", "news");
var result3 = getResponseBodyViewOptions(mediaType3);
expect(result3.$0,kNoBodyViewOptions);
expect(result3.$1,null);
expect(result3.$1, kNoBodyViewOptions);
expect(result3.$2, null);
});
test('Testing getResponseBodyViewOptions for application/calendar+json', () {
test('Testing getResponseBodyViewOptions for application/calendar+json',
() {
MediaType mediaType4 = MediaType("application", "calendar+json");
var result4 = getResponseBodyViewOptions(mediaType4);
expect(result4.$0,kCodeRawBodyViewOptions);
expect(result4.$1, "json");
expect(result4.$1, kCodeRawBodyViewOptions);
expect(result4.$2, "json");
});
test('Testing getResponseBodyViewOptions for image/svg+xml', () {
MediaType mediaType5 = MediaType("image", "svg+xml");
var result5 = getResponseBodyViewOptions(mediaType5);
expect(result5.$0,kCodeRawBodyViewOptions);
expect(result5.$1, "xml");
expect(result5.$1, kCodeRawBodyViewOptions);
expect(result5.$2, "xml");
});
test('Testing getResponseBodyViewOptions for application/xhtml+xml', () {
MediaType mediaType6 = MediaType("application", "xhtml+xml");
var result6 = getResponseBodyViewOptions(mediaType6);
expect(result6.$0,kCodeRawBodyViewOptions);
expect(result6.$1, "xml");
expect(result6.$1, kCodeRawBodyViewOptions);
expect(result6.$2, "xml");
});
test('Testing getResponseBodyViewOptions for application/xml-external-parsed-entity', () {
MediaType mediaType7 = MediaType("application", "xml-external-parsed-entity");
test(
'Testing getResponseBodyViewOptions for application/xml-external-parsed-entity',
() {
MediaType mediaType7 =
MediaType("application", "xml-external-parsed-entity");
var result7 = getResponseBodyViewOptions(mediaType7);
expect(result7.$0,kCodeRawBodyViewOptions);
expect(result7.$1, "xml");
expect(result7.$1, kCodeRawBodyViewOptions);
expect(result7.$2, "xml");
});
test('Testing getResponseBodyViewOptions for text/html', () {
MediaType mediaType8 = MediaType("text", "html");
var result8 = getResponseBodyViewOptions(mediaType8);
expect(result8.$0,kCodeRawBodyViewOptions);
expect(result8.$1, "xml");
expect(result8.$1, kCodeRawBodyViewOptions);
expect(result8.$2, "xml");
});
test('Testing getResponseBodyViewOptions for application/pdf', () {
MediaType mediaType9 = MediaType("application", "pdf");
var result9 = getResponseBodyViewOptions(mediaType9);
expect(result9.$0,kNoBodyViewOptions);
expect(result9.$1, "pdf");
expect(result9.$1, kNoBodyViewOptions);
expect(result9.$2, "pdf");
});
test('Testing getResponseBodyViewOptions for text/calendar', () {
test('Testing getResponseBodyViewOptions for text/calendar', () {
MediaType mediaType10 = MediaType("text", "calendar");
var result10 = getResponseBodyViewOptions(mediaType10);
expect(result10.$0,kRawBodyViewOptions);
expect(result10.$1, "calendar");
expect(result10.$1, kRawBodyViewOptions);
expect(result10.$2, "calendar");
});
});
group("Testing formatBody", () {
test('Testing formatBody for null values', () {
expect(formatBody(null, null),null);
expect(formatBody(null, null), null);
});
test('Testing formatBody for null body values', () {
MediaType mediaType1 = MediaType("application", "xml");
expect(formatBody(null, mediaType1),null);
expect(formatBody(null, mediaType1), null);
});
test('Testing formatBody for null MediaType values', () {
String body1 = '''
@ -314,7 +320,7 @@ void main() {
"text":"The Chosen One";
}
''';
expect(formatBody(body1, null),null);
expect(formatBody(body1, null), null);
});
test('Testing formatBody for json subtype values', () {
String body2 = '''{"data":{"area":9831510.0,"population":331893745}}''';
@ -325,7 +331,7 @@ void main() {
"population": 331893745
}
}''';
expect(formatBody(body2, mediaType2),result2Expected);
expect(formatBody(body2, mediaType2), result2Expected);
});
test('Testing formatBody for xml subtype values', () {
String body3 = '''
@ -347,28 +353,32 @@ void main() {
<calories>650</calories>
</food>
</breakfast_menu>''';
expect(formatBody(body3, mediaType3),result3Expected);
expect(formatBody(body3, mediaType3), result3Expected);
});
group("Testing formatBody for html", () {
MediaType mediaTypeHtml = MediaType("text", "html");
test('Testing formatBody for html subtype values', () {
String body4 = '''<html>
String body4 = '''<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>''';
expect(formatBody(body4, mediaTypeHtml),body4);
</html>''';
expect(formatBody(body4, mediaTypeHtml), body4);
});
test('Testing formatBody for html subtype values with random values', () {
String body5 =
'''<html>${RandomStringGenerator.getRandomStringLines(100, 10000)}</html>''';
expect(formatBody(body5, mediaTypeHtml), null);
});
test(
'Testing formatBody for html subtype values with random values within limit',
() {
String body6 =
'''<html>${RandomStringGenerator.getRandomStringLines(100, 190)}</html>''';
expect(formatBody(body6, mediaTypeHtml), body6);
});
});
test('Testing formatBody for html subtype values with random values', () {
String body5 = '''<html>${getRandomStringLines(100, 10000)}</html>''';
expect(formatBody(body5, mediaTypeHtml),null);
});
test('Testing formatBody for html subtype values with random values within limit', () {
String body6 = '''<html>${getRandomStringLines(100, 190)}</html>''';
expect(formatBody(body6, mediaTypeHtml),body6);
});
});
});
}

View File

@ -0,0 +1,62 @@
import 'package:test/test.dart';
import 'package:apidash/utils/window_utils.dart';
void main() {
group(
"Testing showButtonLabelsInBodySuccess function",
() {
test('Test case 1 options 0', () {
expect(showButtonLabelsInBodySuccess(0, 300), true);
});
test('Test case 2 options 0', () {
expect(showButtonLabelsInBodySuccess(0, 500), true);
});
test('Test case 1 options 1', () {
expect(showButtonLabelsInBodySuccess(1, 250), false);
});
test('Test case 2 options 1', () {
expect(showButtonLabelsInBodySuccess(1, 350), true);
});
test('Test case 1 options 2', () {
expect(showButtonLabelsInBodySuccess(2, 250), false);
});
test('Test case 2 options 2', () {
expect(showButtonLabelsInBodySuccess(2, 350), false);
});
test('Test case 3 options 2', () {
expect(showButtonLabelsInBodySuccess(2, 450), true);
});
test('Test case 1 options 3', () {
expect(showButtonLabelsInBodySuccess(3, 350), false);
});
test('Test case 2 options 3', () {
expect(showButtonLabelsInBodySuccess(3, 450), false);
});
test('Test case 3 options 3', () {
expect(showButtonLabelsInBodySuccess(3, 550), true);
});
},
);
group(
"Testing showButtonLabelsInViewCodePane function",
() {
test('Test case 2', () {
expect(showButtonLabelsInViewCodePane(350), false);
});
test('Test case 3', () {
expect(showButtonLabelsInViewCodePane(450), true);
});
},
);
}

1
test/widget_test.dart Normal file
View File

@ -0,0 +1 @@
void main() {}

View File

@ -50,7 +50,7 @@ void main() async {
});
testWidgets('Testing for code previewer when code is of 1000 lines',
(tester) async {
String codeLines = getRandomStringLines(1000, 20);
String codeLines = RandomStringGenerator.getRandomStringLines(1000, 20);
await tester.pumpWidget(
MaterialApp(
title: 'Code Previewer',

View File

@ -1,6 +1,7 @@
import 'package:apidash/widgets/widgets.dart'
show ViewCodePane, CodeGenPreviewer;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/codegen_previewer.dart';
import 'package:apidash/consts.dart';
import '../test_consts.dart';
@ -58,6 +59,8 @@ void main() async {
Expanded(
child: ViewCodePane(
code: code,
codegenLanguage: CodegenLanguage.dartHttp,
onChangedCodegenLanguage: (p0) {},
),
),
],
@ -66,7 +69,18 @@ void main() async {
);
await tester.pumpAndSettle();
expect(find.text('Code'), findsOneWidget);
expect(find.byType(DropdownButton<CodegenLanguage>), findsOneWidget);
expect(
(tester.widget(find.byType(DropdownButton<CodegenLanguage>))
as DropdownButton)
.value,
equals(CodegenLanguage.dartHttp));
await tester.tap(find.text('Dart (http)'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Kotlin (OkHttp)'), findsWidgets);
expect(find.textContaining('Error Status Code', findRichText: true),
findsOneWidget);
@ -84,6 +98,8 @@ void main() async {
Expanded(
child: ViewCodePane(
code: code,
codegenLanguage: CodegenLanguage.dartHttp,
onChangedCodegenLanguage: (p0) {},
),
),
],
@ -92,7 +108,7 @@ void main() async {
);
await tester.pumpAndSettle();
expect(find.text('Code'), findsOneWidget);
expect(find.text('Dart (http)'), findsOneWidget);
expect(find.textContaining('Error Status Code', findRichText: true),
findsOneWidget);

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/previewer.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import 'package:flutter/foundation.dart';
import '../test_consts.dart';
@ -33,9 +33,7 @@ void main() {
),
);
expect(
find.text("${kMimeTypeRaiseIssueStart}audio/mpeg$kMimeTypeRaiseIssue"),
findsOneWidget);
expect(find.byType(Uint8AudioPlayer), findsOneWidget);
});
testWidgets('Testing when type/subtype is video/H264', (tester) async {
@ -120,4 +118,21 @@ void main() {
await tester.pumpAndSettle();
expect(find.text(kImageError), findsOneWidget);
});
testWidgets('Testing when type/subtype is audio/mpeg corrupted',
(tester) async {
Uint8List bytesAudioCorrupt =
Uint8List.fromList(List.generate(100, (index) => index));
await tester.pumpWidget(
MaterialApp(
title: 'Previewer',
home: Scaffold(
body: Previewer(
type: 'audio', subtype: 'mpeg', bytes: bytesAudioCorrupt),
),
),
);
await tester.pumpAndSettle();
expect(find.text(kAudioError), findsOneWidget);
});
}

View File

@ -204,8 +204,9 @@ void main() {
url: 'api.foss42.com/case/lower',
name: 'foss42 api',
requestHeaders: [
KVRow('content-length', '18'),
KVRow('content-type', 'application/json; charset=utf-8')
NameValueModel(name: 'content-length', value: '18'),
NameValueModel(
name: 'content-type', value: 'application/json; charset=utf-8')
],
requestBodyContentType: ContentType.json,
requestBody: '''{

View File

@ -6,12 +6,12 @@ import 'package:multi_split_view/multi_split_view.dart';
void main() {
testWidgets('Testing for Dashboard Splitview', (tester) async {
await tester.pumpWidget(
MaterialApp(
const MaterialApp(
title: 'Dashboard Splitview',
home: Scaffold(
body: DashboardSplitView(
sidebarWidget: Column(children: const [Text("Hello")]),
mainWidget: Column(children: const [Text("World")]),
sidebarWidget: Column(children: [Text("Hello")]),
mainWidget: Column(children: [Text("World")]),
),
),
),
@ -23,12 +23,12 @@ void main() {
});
testWidgets('Testing for Equal SplitView', (tester) async {
await tester.pumpWidget(
MaterialApp(
const MaterialApp(
title: 'Equal SplitView',
home: Scaffold(
body: EqualSplitView(
leftWidget: Column(children: const [Text("Hello equal")]),
rightWidget: Column(children: const [Text("World equal")]),
leftWidget: Column(children: [Text("Hello equal")]),
rightWidget: Column(children: [Text("World equal")]),
),
),
),

View File

@ -10,8 +10,8 @@ void main() {
MaterialApp(
title: 'URL Field',
theme: kThemeDataDark,
home: Scaffold(
body: Column(children: const [URLField(activeId: '2')]),
home: const Scaffold(
body: Column(children: [URLField(activeId: '2')]),
),
),
);
@ -30,9 +30,9 @@ void main() {
MaterialApp(
title: 'CellField',
theme: kThemeDataLight,
home: Scaffold(
home: const Scaffold(
body: Column(
children: const [
children: [
CellField(
keyId: "4",
hintText: "Passing some hint text",