Compare commits
130 Commits
v0.5.0
..
c8a06f3601
| Author | SHA1 | Date | |
|---|---|---|---|
| c8a06f3601 | |||
| 50d4ddfa28 | |||
| 368f7505ff | |||
| ea73f1d3d4 | |||
| d69056e5d6 | |||
| 051f30b097 | |||
| 6a9fd0999c | |||
|
|
d8ac94d067 | ||
| 0532d59746 | |||
| e0fcbc71e1 | |||
| c072c57bbb | |||
| aadd6c8abf | |||
| 316ed64315 | |||
| 7084ca1be2 | |||
| 317daddb8e | |||
| 8ef291efd8 | |||
| 92be9c50bf | |||
| f0e5ecd5de | |||
| e2c44583e8 | |||
| 5da741f3a9 | |||
| 35c5eb65cb | |||
| 7e53a26e5f | |||
|
|
436b3f85d0 | ||
| 9c1a9cc3c5 | |||
| 82f30f126d | |||
|
|
9599ac28ca | ||
|
|
e71a699ed8 | ||
| ff4eb8ca98 | |||
| b69973b9af | |||
| 3e133fa147 | |||
| ae506789ab | |||
| c3fa689617 | |||
| b51e42b4b2 | |||
| d4bea91186 | |||
| d47dadeb88 | |||
| b807904c6c | |||
| ee1bee22eb | |||
| f6c4c1623f | |||
| 49fd086b92 | |||
| 35dce0bf01 | |||
| 71240373c0 | |||
| 659023d561 | |||
| a0073b65ad | |||
| cba53e0841 | |||
| e50fb88bfc | |||
| ad58912baf | |||
| c4e8d64710 | |||
| ca4319001c | |||
| ebc58b831d | |||
|
|
b1572c903c | ||
|
|
5b73924e2a | ||
| 97ce258a8d | |||
| fedec79a95 | |||
| 8e74709b9c | |||
|
|
be09e1d932 | ||
|
|
d95417d075 | ||
| 2e3e511e3b | |||
| 8555052cc4 | |||
| 80bc6793c7 | |||
| 049a0c5d49 | |||
| ae9cb77e6d | |||
| 126ed5ed72 | |||
| 63ae64cebd | |||
| 0b29351366 | |||
| 4ea39f74fe | |||
| 76fcf5e67f | |||
| 11457736e6 | |||
| e2a6af1cbd | |||
| 20ea15009d | |||
| 00ab0f27f7 | |||
| 1f4870d082 | |||
| e96af7410e | |||
| 345bb8ce03 | |||
| dbcfc77ad4 | |||
| e653532212 | |||
| 8e7e31f64d | |||
| bff3795cc6 | |||
| 8782f1353d | |||
| f08f255a46 | |||
| 02870043ec | |||
| 154e491922 | |||
| ef5e702255 | |||
| cb4cd93bcd | |||
|
|
ee034c9caf | ||
| c133a4ecd2 | |||
| f09a2efa5e | |||
| 49983f4173 | |||
| 0f9894e1be | |||
|
|
8c0dffec31 | ||
|
|
0085d944ea | ||
|
|
89cf0e66a6 | ||
|
|
baef436f78 | ||
| cf00d7992e | |||
| f25829f3c1 | |||
| 8c783bc405 | |||
| 0048d71b74 | |||
| c633347ecc | |||
|
|
ecd6a0ec32 | ||
| 30507d9d01 | |||
| 6245a794d5 | |||
| 5c822e4890 | |||
| cab06fe43f | |||
| b4ff5f3351 | |||
| 0834802481 | |||
| 3afd74dcbf | |||
| b1a0bdfbb6 | |||
| 6d38bc5e1d | |||
| 5ba1ba15c9 | |||
| db05d2abfb | |||
| 1840c4e39a | |||
| c5a3f424d6 | |||
| 04aa6b81b5 | |||
| 5ff3b9b996 | |||
| 228e4a61a4 | |||
| df38ea5413 | |||
| 709f6ca6ca | |||
| b012fc29e4 | |||
| bdad723aef | |||
| f97d46cec3 | |||
| 7381eaef57 | |||
| 72c922b311 | |||
|
|
fd14a8152c | ||
| 5cb60c317d | |||
| 847de75713 | |||
| 58723cf3e8 | |||
| c613168bfb | |||
|
|
6f83de77f2 | ||
|
|
3f6ef3beb4 | ||
| 14e50c1465 | |||
| 0aa9fdca14 |
@@ -0,0 +1,4 @@
|
||||
--artifact-server-path=./.act/artifacts
|
||||
--cache-server-path=./.act/cache
|
||||
--container-options --privileged
|
||||
--env ACT=true
|
||||
@@ -7,8 +7,5 @@ echo "Running pre-push hook:"
|
||||
echo "Executing: cargo fmt"
|
||||
cargo fmt
|
||||
|
||||
echo "Executing: make lint"
|
||||
make lint
|
||||
|
||||
echo "Executing: cargo test"
|
||||
cargo test
|
||||
echo "Executing: cargo clippy --all"
|
||||
cargo clippy --all
|
||||
|
||||
@@ -7,8 +7,8 @@ echo "Running pre-push hook:"
|
||||
echo "Executing: cargo fmt --check"
|
||||
cargo fmt --check
|
||||
|
||||
echo "Executing: make lint"
|
||||
make lint
|
||||
echo "Executing: cargo clippy --all"
|
||||
cargo clippy --all
|
||||
|
||||
echo "Executing: cargo test"
|
||||
cargo test
|
||||
echo "Executing: cargo test --all"
|
||||
cargo test --all
|
||||
|
||||
@@ -76,15 +76,15 @@ jobs:
|
||||
RUSTDOCFLAGS: --cfg docsrs
|
||||
msrv:
|
||||
# check that we can build using the minimal rust version that is specified by this crate
|
||||
name: 1.82.0 / check
|
||||
name: 1.89.0 / check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install 1.82.0
|
||||
- name: Install 1.89.0
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.82.0
|
||||
toolchain: 1.89.0
|
||||
|
||||
- name: cargo +1.82.0 check
|
||||
- name: cargo +1.89.0 check
|
||||
run: cargo check
|
||||
|
||||
+210
-21
@@ -8,9 +8,9 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bump_type:
|
||||
description: "Specify the type of version bump"
|
||||
description: 'Specify the type of version bump'
|
||||
required: true
|
||||
default: "patch"
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Configure SSH for Git
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.RELEASE_BOT_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
@@ -30,20 +31,27 @@ jobs:
|
||||
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Checkout repository
|
||||
if: env.ACT != 'true'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout repository
|
||||
if: env.ACT == 'true'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install Commitizen
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install commitizen
|
||||
python3 -m pip install --upgrade pip
|
||||
pip3 install commitizen==4.8.3
|
||||
npm install -g conventional-changelog-cli
|
||||
|
||||
- name: Configure Git user
|
||||
@@ -62,12 +70,6 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Update the Cargo.lock
|
||||
run: |
|
||||
cargo update
|
||||
git add Cargo.lock
|
||||
git commit -m "chore: Bump the version in Cargo.lock"
|
||||
|
||||
- name: Get the new version tag
|
||||
id: version
|
||||
run: |
|
||||
@@ -84,14 +86,133 @@ jobs:
|
||||
echo "Previous tag: $PREV_TAG"
|
||||
echo "prev_version=$PREV_TAG" >> $GITHUB_ENV
|
||||
|
||||
- name: Bump Cargo.toml version
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
VERSION: ${{ env.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
: "${VERSION:?env.version is empty}"
|
||||
|
||||
# Ignore Act's local artifact dir noise
|
||||
echo artifacts/ >> .git/info/exclude || true
|
||||
|
||||
# Edit the version line right after name="managarr"
|
||||
sed -E -i '
|
||||
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
|
||||
n
|
||||
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||
}
|
||||
' Cargo.toml
|
||||
|
||||
cargo update || true
|
||||
|
||||
# Git config that helps in containers (Act)
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
# Debug: show what changed
|
||||
git status --porcelain
|
||||
git diff --name-only -- Cargo.toml Cargo.lock || true
|
||||
|
||||
# Only commit if one of these files actually changed
|
||||
if ! git diff --quiet -- Cargo.toml Cargo.lock; then
|
||||
# Stage only modifications of already tracked files (won't pick up artifacts/)
|
||||
git add -u -- Cargo.toml Cargo.lock
|
||||
git commit -m "chore: bump Cargo.toml to $VERSION"
|
||||
else
|
||||
echo "No changes to commit (already at $VERSION)"
|
||||
fi
|
||||
|
||||
- name: Bump validate_theme_derive/Cargo.toml version
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/proc_macros/validate_theme_derive
|
||||
env:
|
||||
VERSION: ${{ env.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
: "${VERSION:?env.version is empty}"
|
||||
|
||||
# Ignore Act's local artifact dir noise
|
||||
echo artifacts/ >> .git/info/exclude || true
|
||||
|
||||
# Edit the version line right after name="managarr"
|
||||
sed -E -i '
|
||||
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
|
||||
n
|
||||
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||
}
|
||||
' Cargo.toml
|
||||
|
||||
cargo update || true
|
||||
|
||||
# Git config that helps in containers (Act)
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
# Debug: show what changed
|
||||
git status --porcelain
|
||||
git diff --name-only -- Cargo.toml || true
|
||||
|
||||
# Only commit if one of these files actually changed
|
||||
if ! git diff --quiet -- Cargo.toml; then
|
||||
# Stage only modifications of already tracked files (won't pick up artifacts/)
|
||||
git add -u -- Cargo.toml
|
||||
git commit -m "chore: bump validate_theme_derive Cargo.toml to $VERSION"
|
||||
else
|
||||
echo "No changes to commit (already at $VERSION)"
|
||||
fi
|
||||
|
||||
- name: Bump enum_display_style_derive/Cargo.toml version
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}/proc_macros/enum_display_style_derive
|
||||
env:
|
||||
VERSION: ${{ env.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
: "${VERSION:?env.version is empty}"
|
||||
|
||||
# Ignore Act's local artifact dir noise
|
||||
echo artifacts/ >> .git/info/exclude || true
|
||||
|
||||
# Edit the version line right after name="managarr"
|
||||
sed -E -i '
|
||||
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
|
||||
n
|
||||
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||
}
|
||||
' Cargo.toml
|
||||
|
||||
cargo update || true
|
||||
|
||||
# Git config that helps in containers (Act)
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
# Debug: show what changed
|
||||
git status --porcelain
|
||||
git diff --name-only -- Cargo.toml || true
|
||||
|
||||
# Only commit if one of these files actually changed
|
||||
if ! git diff --quiet -- Cargo.toml; then
|
||||
# Stage only modifications of already tracked files (won't pick up artifacts/)
|
||||
git add -u -- Cargo.toml
|
||||
git commit -m "chore: bump enum_display_style_derive Cargo.toml to $VERSION"
|
||||
else
|
||||
echo "No changes to commit (already at $VERSION)"
|
||||
fi
|
||||
|
||||
- name: Generate changelog for the version bump
|
||||
id: changelog
|
||||
run: |
|
||||
changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }})
|
||||
echo "$changelog" > artifacts/changelog.md
|
||||
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
|
||||
conventional-changelog -p angular -i CHANGELOG.md --from ${{ env.prev_version }} --to v${{ env.version }} > artifacts/changelog.md
|
||||
|
||||
- name: Push changes
|
||||
if: env.ACT != 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -102,6 +223,17 @@ jobs:
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Upload the changed Cargo files (Act)
|
||||
if: env.ACT == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bumped-cargo-files
|
||||
path: |
|
||||
Cargo.toml
|
||||
Cargo.lock
|
||||
proc_macros/validate_theme_derive/Cargo.toml
|
||||
proc_macros/enum_display_style_derive/Cargo.toml
|
||||
|
||||
build-release-artifacts:
|
||||
name: build-release
|
||||
needs: [bump-version]
|
||||
@@ -129,7 +261,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner }}
|
||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
@@ -140,10 +272,18 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Ensure repository is up-to-date
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
git fetch --all
|
||||
git pull
|
||||
|
||||
- name: Get bumped Cargo files (Act)
|
||||
if: env.ACT == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bumped-cargo-files
|
||||
path: ${{ github.workspace }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Cache Cargo registry
|
||||
with:
|
||||
@@ -246,6 +386,12 @@ jobs:
|
||||
needs: [build-release-artifacts]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -258,6 +404,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Ensure repository is up-to-date
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
git fetch --all
|
||||
git pull
|
||||
@@ -266,10 +413,14 @@ jobs:
|
||||
run: |
|
||||
release_version="$(cat ./artifacts/release-version)"
|
||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||
changelog_body="$(cat ./artifacts/changelog.md)"
|
||||
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
|
||||
|
||||
- name: Validate release environment variables
|
||||
run: |
|
||||
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||
echo "Changelog body: $(cat artifacts/changelog.md)"
|
||||
|
||||
- name: Create a GitHub Release
|
||||
if: env.ACT != 'true'
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -300,8 +451,8 @@ jobs:
|
||||
artifacts/managarr-armv7-musl.tar.gz
|
||||
artifacts/managarr-armv7-musl.sha256
|
||||
tag_name: v${{ env.RELEASE_VERSION }}
|
||||
name: "v${{ env.RELEASE_VERSION }}"
|
||||
body: ${{ env.changelog_body }}
|
||||
name: 'v${{ env.RELEASE_VERSION }}'
|
||||
body_path: artifacts/changelog.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
@@ -309,12 +460,19 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
overwrite: true
|
||||
|
||||
publish-chocolatey-package:
|
||||
needs: [publish-github-release]
|
||||
name: Publish Chocolatey Package
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -343,6 +501,7 @@ jobs:
|
||||
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||
|
||||
- name: Package and Publish package to Chocolatey
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
mkdir ./deployment/chocolatey/tools
|
||||
# Run packaging script
|
||||
@@ -362,6 +521,12 @@ jobs:
|
||||
name: Update Homebrew formulas
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -394,11 +559,13 @@ jobs:
|
||||
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||
|
||||
- name: Execute Homebrew packaging script
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
# run packaging script
|
||||
python "./deployment/homebrew/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/homebrew/managarr.rb.template" "./managarr.rb" ${{ env.MACOS_SHA }} ${{ env.MACOS_SHA_ARM }} ${{ env.LINUX_SHA }}
|
||||
|
||||
- name: Push changes to Homebrew tap
|
||||
if: env.ACT != 'true'
|
||||
env:
|
||||
TOKEN: ${{ secrets.MANAGARR_GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -418,6 +585,12 @@ jobs:
|
||||
name: Publishing Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -429,6 +602,12 @@ jobs:
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Ensure repository is up-to-date
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
git fetch --all
|
||||
git pull
|
||||
|
||||
- name: Set version variable
|
||||
run: |
|
||||
version="$(cat artifacts/release-version)"
|
||||
@@ -445,6 +624,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: env.ACT != 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
@@ -456,7 +636,7 @@ jobs:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
push: ${{ env.ACT != 'true' }}
|
||||
tags: darkalex17/managarr:latest, darkalex17/managarr:${{ env.version }}
|
||||
|
||||
publish-crate:
|
||||
@@ -465,7 +645,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if actor is repository owner
|
||||
if: ${{ github.actor != github.repository_owner }}
|
||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||
run: |
|
||||
echo "You are not authorized to run this workflow."
|
||||
exit 1
|
||||
@@ -475,7 +655,15 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get bumped Cargo files (Act)
|
||||
if: env.ACT == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bumped-cargo-files
|
||||
path: ${{ github.workspace }}
|
||||
|
||||
- name: Ensure repository is up-to-date
|
||||
if: env.ACT != 'true'
|
||||
run: |
|
||||
git fetch --all
|
||||
git pull
|
||||
@@ -495,5 +683,6 @@ jobs:
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: katyo/publish-crates@v2
|
||||
if: env.ACT != 'true'
|
||||
with:
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
@@ -175,6 +175,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Upload to codecov.io
|
||||
if: env.ACT != 'true'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/target
|
||||
/.idea/
|
||||
/.scannerwork/
|
||||
/.act/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[keys.normal.backspace]
|
||||
b = ":sh zellij run -x '4%%' --width '92%%' -f -n Build -- just build"
|
||||
r = ":sh zellij run -x '3%%' -y '8%%' --width '95%%' --height '90%%' -fc -n 'Run' -- just run"
|
||||
t = ":sh zellij run -x '4%%' --width '92%%' -f -n Tests -- just test"
|
||||
l = ":sh zellij run -x '4%%' --width '92%%' -f -n Lint -- just lint"
|
||||
+74
-1
@@ -5,7 +5,80 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## v0.5.0 (2025-03-01)
|
||||
## v0.6.3 (2025-12-13)
|
||||
|
||||
### Fix
|
||||
|
||||
- Wrapped all Sonarr use of Language with Option to fix the 'null' array issue in the new Sonarr API
|
||||
|
||||
## v0.6.2 (2025-12-12)
|
||||
|
||||
### Fix
|
||||
|
||||
- Fixed breaking Sonarr Episode file API calls after recent Sonarr API update
|
||||
|
||||
### Refactor
|
||||
|
||||
- Replaced all modulo usages of tick_until_poll with is_multiple_of
|
||||
|
||||
## v0.6.1 (2025-09-02)
|
||||
|
||||
### Fix
|
||||
|
||||
- Fixed UI bugs introduced as part of the hotkey refactor
|
||||
|
||||
### Refactor
|
||||
|
||||
- Updated crate to publish properly with the procedural macros
|
||||
|
||||
## v0.6.0 (2025-08-29)
|
||||
|
||||
### Feat
|
||||
|
||||
- Support for custom headers to be added to every request to each server to support alternative authentication mechanisms [#47]
|
||||
- Refactor all keybinding tips into a dynamically changing menu that can be invoked via '?' [#32]
|
||||
- Display total disk usage for series in the Library view to mirror Radarr functionality [#44]
|
||||
- Pagination support for jumping 20 items at a time in all table views [#45]
|
||||
- Support toggling Movie monitoring directly from the library view [#43]
|
||||
- Support toggling Movie monitoring from the CLI
|
||||
- Support toggling Series monitoring directly from the Sonarr library view [#43]
|
||||
- Support toggling Series monitoring from the CLI
|
||||
- Fixed the Radarr downloads tab to display more than 10 downloads at a time and added a new --count flag to the CLI for specifying the number of downloads to return
|
||||
- Fetch more than 10 downloads when listing Sonarr downloads, and add a --count flag to the CLI to specify how many downloads to fetch
|
||||
- Support alternative keymappings for all keys, featuring hjkl movements
|
||||
- Added the Eldritch theme and updated documentation
|
||||
- Write built in themes to the themes file on first run so users can define custom themes
|
||||
- Created a theme validation macro to verify theme configurations before allowing the TUI to start
|
||||
- Initial support for custom user-defined themes
|
||||
|
||||
### Fix
|
||||
|
||||
- Marked Radarr studios as nullable to prevent crashes
|
||||
- Fixed a bug where the Sonarr API was returning empty values for seeders when searching for season releases
|
||||
- Improve fault tolerance for tag associations in Radarr and Sonarr
|
||||
- Upgraded to the most recent version of Tokio to mitigate CWE-664
|
||||
- Updated all dependencies and updated openssl to the most recent version to mitigate CWE-416
|
||||
- Updated the name of the should_ignore_quit_key to ignore_special_keys_for_textbox_input to give a better idea of what the flag is used for; also added alt keybinding for backspace
|
||||
- Marked videoCodecs as Option to resolve #38
|
||||
- Marked the `Season.statistics` field as `Option` so that a panic does not happen for outdated Sonarr data. This resolves #35
|
||||
- When adding a film from the Collection Details modal, the render order was wrong: Radarr Library -> Collection Table -> Add Movie Prompt (missing the Collection details prompt too). Correct order is: Collection Table -> Collection Details Modal -> Add Movie Modal
|
||||
- Fixed a bug that was rendering encompassing blocks after other widgets were rendered, thus overwriting the custom styles on each previously rendered widget
|
||||
- change the name of the theme configuration file to 'themes'
|
||||
- Ensure key events are only processed on key press to avoid duplicates
|
||||
- Updated ring dependency to mitigate CWE-770
|
||||
- Modified the Sonarr DownloadRecord so that the episode_id is optional to prevent crashes for weird downloads
|
||||
|
||||
### Refactor
|
||||
|
||||
- Network module is now broken out into similar directory structures for each servarr to mimic the rest of the project to make it easier to develop and maintain
|
||||
- Refactored the IndexerTestResut model into the general Servarr models
|
||||
- Renamed 'ctrl-*' keyboard shortcuts to 'C-*' to simplify and shrink the on-screen help
|
||||
- Formatted files using rustfmt
|
||||
- Reformatted code to make the format checks pass
|
||||
- Created a derive macro for defining the display style of Enum models and removed the use of the EnumDisplayStyle trait
|
||||
- Expanded the serde_enum_from macro to further reduce code duplication
|
||||
|
||||
## v0.5.1 (2025-03-01)
|
||||
|
||||
### Feat
|
||||
|
||||
|
||||
+23
-2
@@ -63,12 +63,33 @@ cz commit
|
||||
## Setup workspace
|
||||
|
||||
1. Clone this repo
|
||||
2. Run `cargo test` to setup hooks
|
||||
2. Run `cargo test` to set up hooks
|
||||
3. Make changes
|
||||
4. Run the application using `make run` or `cargo run`
|
||||
4. Run the application using `just run` or `just run`
|
||||
- Install `just` (`cargo install just`) if you haven't already to use the [justfile](./justfile) in this project.
|
||||
5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or warnings from Clippy, please fix them.
|
||||
6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that will run lint and test.
|
||||
7. Create a PR
|
||||
|
||||
### CI/CD Testing with Act
|
||||
If you also are planning on testing out your changes before pushing them with [Act](https://github.com/nektos/act), you will need to set up `act`,
|
||||
`docker`, and configure your local system to run different architectures:
|
||||
|
||||
1. Install `docker` by following the instructions on the [official Docker installation page](https://docs.docker.com/get-docker/).
|
||||
2. Install `act` by following the instructions on the [official Act installation page](https://nektosact.com/installation/index.html).
|
||||
3. Install `binfmt` on your system once so that `act` can run the correct architecture for the CI/CD workflows.
|
||||
You can do this by running:
|
||||
```shell
|
||||
sudo docker run --rm --privileged tonistiigi/binfmt --install all
|
||||
```
|
||||
|
||||
Then, you can run workflows locally without having to commit and see if the GitHub action passes or fails.
|
||||
|
||||
**For example**: To test the [release.yml](.github/workflows/release.yml) workflow locally, you can run:
|
||||
|
||||
```shell
|
||||
act -W .github/workflows/release.yml --input_type bump=minor
|
||||
```
|
||||
|
||||
## Questions? Reach out to me!
|
||||
If you encounter any questions while developing Managarr, please don't hesitate to reach out to me at alex.j.tusa@gmail.com. I'm happy to help contributors, new and experienced in any way I can!
|
||||
|
||||
Generated
+1193
-577
File diff suppressed because it is too large
Load Diff
+28
-10
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.5.0"
|
||||
version = "0.6.3"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI and CLI to manage your Servarrs"
|
||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||
@@ -8,11 +8,17 @@ documentation = "https://github.com/Dark-Alex-17/managarr"
|
||||
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
rust-version = "1.82.0"
|
||||
rust-version = "1.89.0"
|
||||
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"proc_macros/enum_display_style_derive",
|
||||
"proc_macros/validate_theme_derive",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
backtrace = "0.3.74"
|
||||
@@ -34,35 +40,45 @@ serde_json = "1.0.91"
|
||||
serde = { version = "1.0.214", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
strum_macros = "0.26.4"
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
tokio-util = "0.7.8"
|
||||
ratatui = { version = "0.29.0", features = [
|
||||
"all-widgets",
|
||||
"unstable-widget-ref",
|
||||
] }
|
||||
urlencoding = "2.1.2"
|
||||
clap = { version = "4.5.20", features = ["derive", "cargo", "env", "wrap_help"] }
|
||||
clap = { version = "4.5.20", features = [
|
||||
"derive",
|
||||
"cargo",
|
||||
"env",
|
||||
"wrap_help",
|
||||
] }
|
||||
clap_complete = "4.5.33"
|
||||
itertools = "0.13.0"
|
||||
itertools = "0.14.0"
|
||||
ctrlc = "3.4.5"
|
||||
colored = "2.1.0"
|
||||
colored = "3.0.0"
|
||||
async-trait = "0.1.83"
|
||||
dirs-next = "2.0.0"
|
||||
managarr-tree-widget = "0.24.0"
|
||||
indicatif = "0.17.9"
|
||||
derive_setters = "0.1.6"
|
||||
deunicode = "1.6.0"
|
||||
paste = "1.0.15"
|
||||
openssl = { version = "0.10.70", features = ["vendored"] }
|
||||
veil = "0.2.0"
|
||||
validate_theme_derive = "0.1.0"
|
||||
enum_display_style_derive = "0.1.0"
|
||||
tachyonfx = { version = "0.21.0", features = ["sendable"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.16"
|
||||
mockall = "0.13.0"
|
||||
mockito = "1.0.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
rstest = "0.23.0"
|
||||
proptest = "1.6.0"
|
||||
rstest = "0.25.0"
|
||||
serial_test = "3.2.0"
|
||||
assertables = "9.8.2"
|
||||
insta = "1.41.1"
|
||||
|
||||
[dev-dependencies.cargo-husky]
|
||||
version = "1"
|
||||
@@ -76,4 +92,6 @@ name = "managarr"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
codegen-units = 3
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
FROM rust:1.85 AS builder
|
||||
FROM rust:1.89 AS builder
|
||||
WORKDIR /usr/src
|
||||
|
||||
# Download and compile Rust dependencies in an empty project and cache as a separate Docker layer
|
||||
@@ -6,6 +6,7 @@ RUN USER=root cargo new --bin managarr-temp
|
||||
|
||||
WORKDIR /usr/src/managarr-temp
|
||||
COPY Cargo.* .
|
||||
COPY proc_macros ./proc_macros
|
||||
RUN cargo build --release
|
||||
# remove src from empty project
|
||||
RUN rm -r src
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#!make
|
||||
VERSION := latest
|
||||
IMG_NAME := darkalex17/managarr
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
default: run
|
||||
|
||||
.PHONY: test test-cov build run lint lint-fix fmt analyze sonar release delete-tag
|
||||
|
||||
test:
|
||||
@cargo test
|
||||
|
||||
## Run all tests with coverage - `cargo install cargo-tarpaulin`
|
||||
test-cov:
|
||||
@cargo tarpaulin
|
||||
|
||||
build: test
|
||||
@cargo build --release
|
||||
|
||||
docker:
|
||||
@DOCKER_BUILDKIT=1 docker build --rm -t ${IMAGE} .
|
||||
|
||||
run:
|
||||
@CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run
|
||||
|
||||
lint:
|
||||
@find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace
|
||||
|
||||
lint-fix:
|
||||
@cargo fix
|
||||
|
||||
fmt:
|
||||
@cargo fmt
|
||||
|
||||
minimal-versions:
|
||||
@cargo +nightly update -Zdirect-minimal-versions
|
||||
|
||||
## Analyze for unsafe usage - `cargo install cargo-geiger`
|
||||
analyze:
|
||||
@cargo geiger
|
||||
|
||||
release:
|
||||
@git tag -a ${V} -m "Release ${V}" && git push origin ${V}
|
||||
|
||||
delete-tag:
|
||||
@git tag -d ${V} && git push --delete origin ${V}
|
||||
|
||||
@@ -28,7 +28,7 @@ Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built
|
||||
- [ ]  [Bazarr](https://www.bazarr.media/)
|
||||
- [ ]  [Tautulli](https://tautulli.com/)
|
||||
|
||||
## Try Before You Buy
|
||||
## Try Out the Demo
|
||||
To try out Managarr before linking it to your HTPC, you can use the purpose built [managarr-demo](https://github.com/Dark-Alex-17/managarr-demo) repository.
|
||||
Simply run the following command to start a demo:
|
||||
|
||||
@@ -63,7 +63,7 @@ Please note that you will need to create and popular your configuration file fir
|
||||
**Note:** If you run into errors using relative file paths when mounting the volume with the configuration file, try using an absolute path.
|
||||
|
||||
### Homebrew (Mac and Linux)
|
||||
To install Managarr from Homebrew, install the Managarr tap and then you'll be able to install Managarr:
|
||||
To install Managarr from Homebrew, install the Managarr tap. Then you'll be able to install Managarr:
|
||||
|
||||
```shell
|
||||
brew tap Dark-Alex-17/managarr
|
||||
@@ -119,16 +119,16 @@ Binaries are available on the [releases](https://github.com/Dark-Alex-17/managar
|
||||
#### Windows Instructions
|
||||
To use a binary from the releases page on Windows, do the following:
|
||||
|
||||
1. Download the latest binary [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
||||
1. Download the latest [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
||||
2. Use 7-Zip or TarTool to unpack the Tar file.
|
||||
3. Run the executable `managarr.exe`!
|
||||
|
||||
#### Linux/MacOS Instructions
|
||||
To use a binary from the releases page on Linux/MacOS, do the following:
|
||||
|
||||
1. Download the latest binary [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
||||
1. Download the latest [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
||||
2. `cd` to the directory where you downloaded the binary.
|
||||
3. Extract the binary with `tar -C /usr/local/bin -xzf managarr-<arch>.tar.gz` (NB: This may require `sudo`)
|
||||
3. Extract the binary with `tar -C /usr/local/bin -xzf managarr-<arch>.tar.gz` (Note: This may require `sudo`)
|
||||
4. Now you can run `managarr`!
|
||||
|
||||
## Features
|
||||
@@ -166,21 +166,21 @@ Key:
|
||||
|
||||
| TUI | CLI | Feature |
|
||||
|-----|-----|--------------------------------------------------------------------------------------------------------------------|
|
||||
| ✅ | ✅ | View your library, downloads, blocklist, episodes |
|
||||
| ✅ | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
|
||||
| ✅ | ✅ | View your library, downloads, blocklist, episodes |
|
||||
| ✅ | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
|
||||
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||
| ✅ | ✅ | Search your library |
|
||||
| ✅ | ✅ | Add series to your library |
|
||||
| ✅ | ✅ | Delete series, downloads, indexers, root folders, and episode files |
|
||||
| ✅ | ✅ | Trigger automatic searches for series, seasons, or episodes |
|
||||
| ✅ | ✅ | Trigger refresh and disk scan for series and downloads |
|
||||
| ✅ | ✅ | Manually search for series, seasons, or episodes |
|
||||
| ✅ | ✅ | Edit your series and indexers |
|
||||
| ✅ | ✅ | Manage your tags |
|
||||
| ✅ | ✅ | Manage your root folders |
|
||||
| ✅ | ✅ | Manage your blocklist |
|
||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||
| ✅ | ✅ | Search your library |
|
||||
| ✅ | ✅ | Add series to your library |
|
||||
| ✅ | ✅ | Delete series, downloads, indexers, root folders, and episode files |
|
||||
| ✅ | ✅ | Trigger automatic searches for series, seasons, or episodes |
|
||||
| ✅ | ✅ | Trigger refresh and disk scan for series and downloads |
|
||||
| ✅ | ✅ | Manually search for series, seasons, or episodes |
|
||||
| ✅ | ✅ | Edit your series and indexers |
|
||||
| ✅ | ✅ | Manage your tags |
|
||||
| ✅ | ✅ | Manage your root folders |
|
||||
| ✅ | ✅ | Manage your blocklist |
|
||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||
|
||||
### Readarr
|
||||
|
||||
@@ -206,6 +206,19 @@ Key:
|
||||
|
||||
- [ ] Support for Tautulli
|
||||
|
||||
### Themes
|
||||
Managarr ships with a few themes out of the box. Here's a few examples:
|
||||
|
||||
#### Default
|
||||

|
||||
#### Dracula
|
||||

|
||||
#### Watermelon Dark
|
||||

|
||||
|
||||
You can also create your own custom themes as well. To learn more about what themes are built-in to Managarr and how
|
||||
to create your own custom themes, check out the [Themes README](themes/README.md).
|
||||
|
||||
### The Managarr CLI
|
||||
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
|
||||
|
||||
@@ -218,7 +231,7 @@ To see all available commands, simply run `managarr --help`:
|
||||
|
||||
```shell
|
||||
$ managarr --help
|
||||
managarr 0.5.0
|
||||
managarr 0.5.1
|
||||
Alex Clarke <alex.j.tusa@gmail.com>
|
||||
|
||||
A TUI and CLI to manage your Servarrs
|
||||
@@ -235,6 +248,8 @@ Commands:
|
||||
Options:
|
||||
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
||||
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
||||
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
||||
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
||||
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
||||
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
||||
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
||||
@@ -315,6 +330,7 @@ managarr --config-file /path/to/config.yml
|
||||
|
||||
### Example Configuration:
|
||||
```yaml
|
||||
theme: default
|
||||
radarr:
|
||||
- host: 192.168.0.78
|
||||
port: 7878
|
||||
@@ -341,6 +357,9 @@ whisparr:
|
||||
port: 6969
|
||||
api_token: someApiToken1234567890
|
||||
ssl_cert_path: /path/to/whisparr.crt
|
||||
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
|
||||
traefik-auth-bypass-key: someBypassKey1234567890
|
||||
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
|
||||
bazarr:
|
||||
- host: 192.168.0.67
|
||||
port: 6767
|
||||
@@ -357,6 +376,7 @@ tautulli:
|
||||
|
||||
### Example Multi-Instance Configuration:
|
||||
```yaml
|
||||
theme: default
|
||||
radarr:
|
||||
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
|
||||
port: 7878
|
||||
|
||||
@@ -12,4 +12,3 @@ coverage:
|
||||
|
||||
ignore:
|
||||
- "**/*_tests.rs"
|
||||
- "src/ui"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
||||
# https://rubydoc.brew.sh/Formula
|
||||
class Managarr < Formula
|
||||
desc "A fast and simple dashboard for Kubernetes written in Rust"
|
||||
desc "Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC)"
|
||||
homepage "https://github.com/Dark-Alex-17/managarr"
|
||||
if OS.mac? and Hardware::CPU.arm?
|
||||
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz"
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
VERSION := "latest"
|
||||
IMG_NAME := "darkalex17/managarr"
|
||||
IMAGE := "{{IMG_NAME}}:{{VERSION}}"
|
||||
|
||||
|
||||
# List all recipes
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Format all files
|
||||
[group: 'style']
|
||||
fmt:
|
||||
@cargo fmt --all
|
||||
|
||||
alias clippy := lint
|
||||
# Run Clippy to inspect all files
|
||||
[group: 'style']
|
||||
lint:
|
||||
@cargo clippy --all
|
||||
|
||||
alias clippy-fix := lint-fix
|
||||
# Automatically fix clippy issues where possible
|
||||
[group: 'style']
|
||||
lint-fix:
|
||||
@cargo fix
|
||||
|
||||
# Analyze the project for unsafe usage
|
||||
[group: 'style']
|
||||
@analyze:
|
||||
#!/usr/bin/env bash
|
||||
cargo geiger -h > /dev/null 2>&1 | cargo install cargo-geiger
|
||||
cargo geiger
|
||||
|
||||
# Run all tests
|
||||
[group: 'test']
|
||||
test:
|
||||
@cargo test --all
|
||||
|
||||
# Run all tests with coverage
|
||||
[group:'test']
|
||||
@test-cov:
|
||||
#!/usr/bin/env bash
|
||||
cargo tarpaulin -h > /dev/null 2>&1 || cargo install cargo-tarpaulin
|
||||
cargo tarpaulin
|
||||
|
||||
# Run all doc tests
|
||||
[group: 'test']
|
||||
doctest:
|
||||
@cargo test --all --doc
|
||||
|
||||
# Run all proptests
|
||||
[group: 'test']
|
||||
proptest:
|
||||
@cargo test proptest
|
||||
|
||||
alias test-snapshots := snapshot-tests
|
||||
# Run all snapshot tests
|
||||
[group: 'test']
|
||||
snapshot-tests:
|
||||
@cargo test snapshot
|
||||
|
||||
alias review := snapshot-review
|
||||
# Review snapshot test changes
|
||||
[group: 'test']
|
||||
@snapshot-review:
|
||||
#!/usr/bin/env bash
|
||||
cargo insta -h > /dev/null 2>&1 || cargo install cargo-insta
|
||||
cargo insta review
|
||||
|
||||
alias clean-orphaned-snapshots := snapshot-delete-unreferenced
|
||||
# Delete any unreferenced snapshots
|
||||
[group: 'test']
|
||||
@snapshot-delete-unreferenced:
|
||||
#!/usr/bin/env bash
|
||||
cargo insta -h > /dev/null 2>&1 || cargo install cargo-insta
|
||||
cargo insta test --unreferenced=delete
|
||||
|
||||
# Build and run the binary for the current system
|
||||
run:
|
||||
@cargo run
|
||||
|
||||
# Build the project for the current system architecture
|
||||
[group: 'build']
|
||||
[arg('build_type', pattern="debug|release")]
|
||||
build build_type='debug':
|
||||
@cargo build {{ if build_type == "release" { "--release" } else { "" } }}
|
||||
|
||||
# Build the docker image
|
||||
[group: 'build']
|
||||
build-docker:
|
||||
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMAGE}}
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "enum_display_style_derive"
|
||||
version = "0.6.1"
|
||||
edition = "2024"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A proc-macro to derive a `Display` and `FromStr` implementation for enums with a `style` attribute."
|
||||
license = "MIT"
|
||||
documentation = "https://github.com/Dark-Alex-17/managarr"
|
||||
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.39"
|
||||
syn = "2.0.99"
|
||||
darling = "0.20.10"
|
||||
@@ -0,0 +1,76 @@
|
||||
mod macro_models;
|
||||
|
||||
use crate::macro_models::DisplayStyleArgs;
|
||||
use darling::FromVariant;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, parse_macro_input};
|
||||
|
||||
/// Derive macro for generating a `to_display_str` method for an enum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Using default values for the display style:
|
||||
///
|
||||
/// ```
|
||||
/// use enum_display_style_derive::EnumDisplayStyle;
|
||||
///
|
||||
/// #[derive(EnumDisplayStyle)]
|
||||
/// enum Weekend {
|
||||
/// Saturday,
|
||||
/// Sunday,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Weekend::Saturday.to_display_str(), "Saturday");
|
||||
/// assert_eq!(Weekend::Sunday.to_display_str(), "Sunday");
|
||||
/// ```
|
||||
///
|
||||
/// Using custom values for the display style:
|
||||
///
|
||||
/// ```
|
||||
/// use enum_display_style_derive::EnumDisplayStyle;
|
||||
///
|
||||
/// #[derive(EnumDisplayStyle)]
|
||||
/// enum MonitorStatus {
|
||||
/// #[display_style(name = "Monitor Transactions")]
|
||||
/// Active,
|
||||
/// #[display_style(name = "Don't Monitor Transactions")]
|
||||
/// None,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(MonitorStatus::Active.to_display_str(), "Monitor Transactions");
|
||||
/// assert_eq!(MonitorStatus::None.to_display_str(), "Don't Monitor Transactions");
|
||||
/// ```
|
||||
#[proc_macro_derive(EnumDisplayStyle, attributes(display_style))]
|
||||
pub fn enum_display_style_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let enum_name = &input.ident;
|
||||
|
||||
let mut match_arms = Vec::new();
|
||||
|
||||
if let Data::Enum(data_enum) = &input.data {
|
||||
let variants = &data_enum.variants;
|
||||
|
||||
for variant in variants {
|
||||
let variant_ident = &variant.ident;
|
||||
let variant_display_name = DisplayStyleArgs::from_variant(variant)
|
||||
.unwrap()
|
||||
.name
|
||||
.unwrap_or_else(|| variant_ident.to_string());
|
||||
|
||||
match_arms.push(quote! {
|
||||
#enum_name::#variant_ident => #variant_display_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl<'a> #enum_name {
|
||||
pub fn to_display_str(self) -> &'a str {
|
||||
match self {
|
||||
#(#match_arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use darling::FromVariant;
|
||||
|
||||
#[derive(Debug, FromVariant)]
|
||||
#[darling(attributes(display_style))]
|
||||
pub struct DisplayStyleArgs {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "validate_theme_derive"
|
||||
version = "0.6.1"
|
||||
edition = "2024"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A proc-macro to validate a theme."
|
||||
license = "MIT"
|
||||
documentation = "https://github.com/Dark-Alex-17/managarr"
|
||||
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.39"
|
||||
syn = "2.0.99"
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4.17"
|
||||
@@ -0,0 +1,106 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
|
||||
/// Derive macro for generating a `validate` method for a Theme struct.
|
||||
/// The `validate` method ensures that all values with the `validate` attribute are not `None`.
|
||||
/// Otherwise, an error message it output to both the log file and stdout and the program exits.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Valid themes pass through the program transitively without any messages being output.
|
||||
///
|
||||
/// ```
|
||||
/// use validate_theme_derive::ValidateTheme;
|
||||
///
|
||||
/// #[derive(ValidateTheme, Default)]
|
||||
/// struct Theme {
|
||||
/// pub name: String,
|
||||
/// #[validate]
|
||||
/// pub good: Option<Style>,
|
||||
/// #[validate]
|
||||
/// pub bad: Option<Style>,
|
||||
/// pub ugly: Option<Style>,
|
||||
/// }
|
||||
///
|
||||
/// struct Style {
|
||||
/// color: String,
|
||||
/// }
|
||||
///
|
||||
/// let theme = Theme {
|
||||
/// good: Some(Style { color: "Green".to_owned() }),
|
||||
/// bad: Some(Style { color: "Red".to_owned() }),
|
||||
/// ..Theme::default()
|
||||
/// };
|
||||
///
|
||||
/// // Since only `good` and `bad` have the `validate` attribute, the `validate` method will only check those fields.
|
||||
/// theme.validate();
|
||||
/// // Since both `good` and `bad` have values, the program will not exit and no message is output.
|
||||
/// ```
|
||||
///
|
||||
/// Invalid themes will output an error message to both the log file and stdout and the program will exit.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use validate_theme_derive::ValidateTheme;
|
||||
///
|
||||
/// #[derive(ValidateTheme, Default)]
|
||||
/// struct Theme {
|
||||
/// pub name: String,
|
||||
/// #[validate]
|
||||
/// pub good: Option<Style>,
|
||||
/// #[validate]
|
||||
/// pub bad: Option<Style>,
|
||||
/// pub ugly: Option<Style>,
|
||||
/// }
|
||||
///
|
||||
/// struct Style {
|
||||
/// color: String,
|
||||
/// }
|
||||
///
|
||||
/// let theme = Theme {
|
||||
/// bad: Some(Style { color: "Red".to_owned() }),
|
||||
/// ..Theme::default()
|
||||
/// };
|
||||
///
|
||||
/// // Since `good` has the `validate` attribute and since `good` is `None`, the `validate` method will output an error message and exit the program.
|
||||
/// theme.validate();
|
||||
/// ```
|
||||
#[proc_macro_derive(ValidateTheme, attributes(validate))]
|
||||
pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let struct_name = &input.ident;
|
||||
|
||||
let mut validation_checks = Vec::new();
|
||||
|
||||
if let Data::Struct(data_struct) = &input.data
|
||||
&& let Fields::Named(fields) = &data_struct.fields
|
||||
{
|
||||
for field in &fields.named {
|
||||
let field_name = &field.ident;
|
||||
|
||||
let has_validate_attr = field
|
||||
.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("validate"));
|
||||
|
||||
if has_validate_attr {
|
||||
validation_checks.push(quote! {
|
||||
if self.#field_name.is_none() {
|
||||
log::error!("{} is missing a color value.", stringify!(#field_name));
|
||||
eprintln!("{} is missing a color value.", stringify!(#field_name));
|
||||
std::process::exit(1);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl #struct_name {
|
||||
pub fn validate(&self) {
|
||||
#(#validation_checks)*
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 56330c025ad79db641d0eb9f429ab74e95822e1fb015b58f0e158ea674cd42a1 # shrinks to list_size = 1, page_ops = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc fb4b58aa3015a125fc33a78dfaf27981db4191247151b327a351fc445e07c231 # shrinks to input = "j"
|
||||
cc d6ec17d4d3f635f0a095ade650a316d26abc1f9fe2b6d9cf67bf2f8b4ebedb60 # shrinks to backspace_count = 0
|
||||
cc cd46ee46e18cf86c940fb89c7206f0b482909880b8f2eabe3dd20682b9912c8a # shrinks to input = "h"
|
||||
@@ -0,0 +1,9 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 24ae243412a324cb46c36cb4f629ddd4c9326b1479d1186d9b5545ac5e86dbba # shrinks to num_scroll_attempts = 0
|
||||
cc c06a1cc1e4740b2498c50d7be64715bf09ef3ac4cf3bb3642f960578a3e06c74 # shrinks to is_loading = false, num_items = 1
|
||||
cc 930207899afea2d389c7fa3974e31c2eb1803e71bcbd8179246c795903905ec7 # shrinks to parent_width = 20, parent_height = 12, percent_x = 1, percent_y = 1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 340 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 241 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 220 KiB |
+155
-46
@@ -2,16 +2,17 @@
|
||||
mod tests {
|
||||
use anyhow::anyhow;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use serde_json::Value;
|
||||
use serial_test::serial;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
|
||||
use crate::app::{interpolate_env_vars, App, AppConfig, Data, ServarrConfig};
|
||||
use crate::app::{App, AppConfig, Data, ServarrConfig, interpolate_env_vars};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||
use crate::models::{HorizontallyScrollableText, TabRoute};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::NetworkEvent;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[test]
|
||||
@@ -31,6 +32,7 @@ mod tests {
|
||||
};
|
||||
let sonarr_config_2 = ServarrConfig::default();
|
||||
let config = AppConfig {
|
||||
theme: None,
|
||||
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
|
||||
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
|
||||
};
|
||||
@@ -38,40 +40,24 @@ mod tests {
|
||||
TabRoute {
|
||||
title: "Sonarr Test".to_owned(),
|
||||
route: ActiveSonarrBlock::default().into(),
|
||||
help: format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
),
|
||||
contextual_help: None,
|
||||
config: Some(sonarr_config_1),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Radarr 1".to_owned(),
|
||||
route: ActiveRadarrBlock::default().into(),
|
||||
help: format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
),
|
||||
contextual_help: None,
|
||||
config: Some(radarr_config_2),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Radarr Test".to_owned(),
|
||||
route: ActiveRadarrBlock::default().into(),
|
||||
help: format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
),
|
||||
contextual_help: None,
|
||||
config: Some(radarr_config_1),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Sonarr 1".to_owned(),
|
||||
route: ActiveSonarrBlock::default().into(),
|
||||
help: format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
),
|
||||
contextual_help: None,
|
||||
config: Some(sonarr_config_2),
|
||||
},
|
||||
@@ -83,9 +69,9 @@ mod tests {
|
||||
CancellationToken::new(),
|
||||
);
|
||||
|
||||
assert!(app.navigation_stack.is_empty());
|
||||
assert_is_empty!(app.navigation_stack);
|
||||
assert_eq!(app.get_current_route(), ActiveSonarrBlock::default().into());
|
||||
assert!(app.network_tx.is_some());
|
||||
assert_some!(app.network_tx);
|
||||
assert!(!app.cancellation_token.is_cancelled());
|
||||
assert!(app.is_first_render);
|
||||
assert_eq!(app.error, HorizontallyScrollableText::default());
|
||||
@@ -94,10 +80,11 @@ mod tests {
|
||||
assert_eq!(app.tick_until_poll, 400);
|
||||
assert_eq!(app.ticks_until_scroll, 4);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||
assert!(!app.is_loading);
|
||||
assert!(!app.is_routing);
|
||||
assert!(!app.should_refresh);
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(!app.cli_mode);
|
||||
}
|
||||
|
||||
@@ -105,8 +92,8 @@ mod tests {
|
||||
fn test_app_default() {
|
||||
let app = App::default();
|
||||
|
||||
assert!(app.navigation_stack.is_empty());
|
||||
assert!(app.network_tx.is_none());
|
||||
assert_is_empty!(app.navigation_stack);
|
||||
assert_none!(app.network_tx);
|
||||
assert!(!app.cancellation_token.is_cancelled());
|
||||
assert!(app.is_first_render);
|
||||
assert_eq!(app.error, HorizontallyScrollableText::default());
|
||||
@@ -117,7 +104,7 @@ mod tests {
|
||||
assert!(!app.is_loading);
|
||||
assert!(!app.is_routing);
|
||||
assert!(!app.should_refresh);
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(!app.cli_mode);
|
||||
}
|
||||
|
||||
@@ -254,6 +241,27 @@ mod tests {
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_on_ui_scroll_tick() {
|
||||
let mut app = App {
|
||||
ticks_until_scroll: 1,
|
||||
..App::default()
|
||||
};
|
||||
|
||||
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
|
||||
app.on_ui_scroll_tick();
|
||||
|
||||
assert_eq!(app.ui_scroll_tick_count, 1);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
|
||||
app.on_ui_scroll_tick();
|
||||
|
||||
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_tick_first_render() {
|
||||
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
||||
@@ -283,7 +291,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
@@ -340,22 +348,59 @@ mod tests {
|
||||
fn test_app_config_default() {
|
||||
let app_config = AppConfig::default();
|
||||
|
||||
assert!(app_config.radarr.is_none());
|
||||
assert!(app_config.sonarr.is_none());
|
||||
assert_none!(app_config.radarr);
|
||||
assert_none!(app_config.sonarr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_servarr_config_default() {
|
||||
let servarr_config = ServarrConfig::default();
|
||||
|
||||
assert_eq!(servarr_config.name, None);
|
||||
assert_eq!(servarr_config.host, Some("localhost".to_string()));
|
||||
assert_eq!(servarr_config.port, None);
|
||||
assert_eq!(servarr_config.uri, None);
|
||||
assert_eq!(servarr_config.weight, None);
|
||||
assert_eq!(servarr_config.api_token, Some(String::new()));
|
||||
assert_eq!(servarr_config.api_token_file, None);
|
||||
assert_eq!(servarr_config.ssl_cert_path, None);
|
||||
assert_none!(servarr_config.name);
|
||||
assert_some_eq_x!(&servarr_config.host, "localhost");
|
||||
assert_none!(servarr_config.port);
|
||||
assert_none!(servarr_config.uri);
|
||||
assert_none!(servarr_config.weight);
|
||||
assert_some_eq_x!(&servarr_config.api_token, "");
|
||||
assert_none!(servarr_config.api_token_file);
|
||||
assert_none!(servarr_config.ssl_cert_path);
|
||||
assert_none!(servarr_config.custom_headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_header_map_basic() {
|
||||
let mut header_map = HeaderMap::new();
|
||||
header_map.insert(
|
||||
HeaderName::from_static("x-api-key"),
|
||||
HeaderValue::from_static("abc123"),
|
||||
);
|
||||
header_map.insert(
|
||||
HeaderName::from_static("header-1"),
|
||||
HeaderValue::from_static("test"),
|
||||
);
|
||||
|
||||
let config = ServarrConfig {
|
||||
custom_headers: Some(header_map),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
|
||||
let v: Value = serde_json::to_value(&config).expect("serialize ok");
|
||||
let custom = v.get("custom_headers").unwrap();
|
||||
assert!(custom.is_object());
|
||||
let obj = custom.as_object().unwrap();
|
||||
|
||||
assert_some_eq_x!(obj.get("x-api-key"), "abc123");
|
||||
assert_some_eq_x!(obj.get("header-1"), "test");
|
||||
|
||||
assert_none!(obj.get("X-Api-Key"));
|
||||
assert_none!(obj.get("HEADER-1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_header_map_none_is_null() {
|
||||
let config = ServarrConfig::default();
|
||||
let v: Value = serde_json::to_value(&config).expect("serialize ok");
|
||||
assert!(v.get("custom_headers").unwrap().is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -369,7 +414,7 @@ mod tests {
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_eq!(config.host, Some("localhost".to_string()));
|
||||
assert_some_eq_x!(&config.host, "localhost");
|
||||
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION") };
|
||||
}
|
||||
|
||||
@@ -384,7 +429,7 @@ mod tests {
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_eq!(config.host, Some("www.example.com".to_string()));
|
||||
assert_some_eq_x!(&config.host, "www.example.com");
|
||||
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE") };
|
||||
}
|
||||
|
||||
@@ -396,7 +441,67 @@ mod tests {
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_eq!(config.port, None);
|
||||
assert_none!(config.port);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_deserialize_optional_env_var_header_map_is_present() {
|
||||
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_HEADER_OPTION", "localhost") };
|
||||
let expected_custom_headers = {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("X-Api-Host", "localhost".parse().unwrap());
|
||||
headers.insert("api-token", "test123".parse().unwrap());
|
||||
headers
|
||||
};
|
||||
let yaml_data = r#"
|
||||
custom_headers:
|
||||
X-Api-Host: ${TEST_VAR_DESERIALIZE_HEADER_OPTION}
|
||||
api-token: "test123"
|
||||
"#;
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_some_eq_x!(&config.custom_headers, &expected_custom_headers);
|
||||
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_deserialize_optional_env_var_header_map_does_not_overwrite_non_env_value() {
|
||||
unsafe {
|
||||
std::env::set_var(
|
||||
"TEST_VAR_DESERIALIZE_HEADER_OPTION_NO_OVERWRITE",
|
||||
"localhost",
|
||||
)
|
||||
};
|
||||
let expected_custom_headers = {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("X-Api-Host", "www.example.com".parse().unwrap());
|
||||
headers.insert("api-token", "test123".parse().unwrap());
|
||||
headers
|
||||
};
|
||||
let yaml_data = r#"
|
||||
custom_headers:
|
||||
X-Api-Host: www.example.com
|
||||
api-token: "test123"
|
||||
"#;
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_some_eq_x!(&config.custom_headers, &expected_custom_headers);
|
||||
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION_NO_OVERWRITE") };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_optional_env_var_header_map_empty() {
|
||||
let yaml_data = r#"
|
||||
api_token: "test123"
|
||||
"#;
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_none!(config.custom_headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -410,7 +515,7 @@ mod tests {
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_eq!(config.port, Some(1));
|
||||
assert_some_eq_x!(config.port, 1);
|
||||
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16") };
|
||||
}
|
||||
|
||||
@@ -425,7 +530,7 @@ mod tests {
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_eq!(config.port, Some(1234));
|
||||
assert_some_eq_x!(config.port, 1234);
|
||||
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16_UNUSED") };
|
||||
}
|
||||
|
||||
@@ -437,9 +542,9 @@ mod tests {
|
||||
"#;
|
||||
let result: Result<ServarrConfig, _> = serde_yaml::from_str(yaml_data);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("invalid digit found in string"));
|
||||
assert_contains!(err, "invalid digit found in string");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -450,7 +555,7 @@ mod tests {
|
||||
|
||||
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||
|
||||
assert_eq!(config.port, None);
|
||||
assert_none!(config.port);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -512,8 +617,11 @@ mod tests {
|
||||
let api_token = "thisisatest".to_owned();
|
||||
let api_token_file = "/root/.config/api_token".to_owned();
|
||||
let ssl_cert_path = "/some/path".to_owned();
|
||||
let expected_str = format!("ServarrConfig {{ name: Some(\"{}\"), host: Some(\"{}\"), port: Some({}), uri: Some(\"{}\"), weight: Some({}), api_token: Some(\"***********\"), api_token_file: Some(\"{}\"), ssl_cert_path: Some(\"{}\") }}",
|
||||
name, host, port, uri, weight, api_token_file, ssl_cert_path);
|
||||
let mut custom_headers = HeaderMap::new();
|
||||
custom_headers.insert("X-Custom-Header", "value".parse().unwrap());
|
||||
let expected_str = format!(
|
||||
"ServarrConfig {{ name: Some(\"{name}\"), host: Some(\"{host}\"), port: Some({port}), uri: Some(\"{uri}\"), weight: Some({weight}), api_token: Some(\"***********\"), api_token_file: Some(\"{api_token_file}\"), ssl_cert_path: Some(\"{ssl_cert_path}\"), custom_headers: Some({{\"x-custom-header\": \"value\"}}) }}"
|
||||
);
|
||||
let servarr_config = ServarrConfig {
|
||||
name: Some(name),
|
||||
host: Some(host),
|
||||
@@ -523,6 +631,7 @@ mod tests {
|
||||
api_token: Some(api_token),
|
||||
api_token_file: Some(api_token_file),
|
||||
ssl_cert_path: Some(ssl_cert_path),
|
||||
custom_headers: Some(custom_headers),
|
||||
};
|
||||
|
||||
assert_str_eq!(format!("{servarr_config:?}"), expected_str);
|
||||
|
||||
@@ -1,20 +1,41 @@
|
||||
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS};
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
|
||||
use crate::app::radarr::radarr_context_clues::RadarrContextClueProvider;
|
||||
use crate::app::sonarr::sonarr_context_clues::SonarrContextClueProvider;
|
||||
use crate::models::Route;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "context_clues_tests.rs"]
|
||||
mod context_clues_tests;
|
||||
|
||||
pub(in crate::app) type ContextClue = (KeyBinding, &'static str);
|
||||
pub type ContextClue = (KeyBinding, &'static str);
|
||||
|
||||
pub fn build_context_clue_string(context_clues: &[(KeyBinding, &str)]) -> String {
|
||||
context_clues
|
||||
.iter()
|
||||
.map(|(key_binding, desc)| format!("{} {desc}", key_binding.key))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" | ")
|
||||
pub trait ContextClueProvider {
|
||||
fn get_context_clues(_app: &mut App<'_>) -> Option<&'static [ContextClue]>;
|
||||
}
|
||||
|
||||
pub static SERVARR_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
pub struct ServarrContextClueProvider;
|
||||
|
||||
impl ContextClueProvider for ServarrContextClueProvider {
|
||||
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||
match app.get_current_route() {
|
||||
Route::Radarr(_, _) => RadarrContextClueProvider::get_context_clues(app),
|
||||
Route::Sonarr(_, _) => SonarrContextClueProvider::get_context_clues(app),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static SERVARR_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||
(DEFAULT_KEYBINDINGS.up, "scroll up"),
|
||||
(DEFAULT_KEYBINDINGS.down, "scroll down"),
|
||||
(DEFAULT_KEYBINDINGS.left, "previous tab"),
|
||||
(DEFAULT_KEYBINDINGS.right, "next tab"),
|
||||
(DEFAULT_KEYBINDINGS.pg_up, DEFAULT_KEYBINDINGS.pg_up.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.pg_down,
|
||||
DEFAULT_KEYBINDINGS.pg_down.desc,
|
||||
),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.next_servarr,
|
||||
DEFAULT_KEYBINDINGS.next_servarr.desc,
|
||||
@@ -24,6 +45,7 @@ pub static SERVARR_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
DEFAULT_KEYBINDINGS.previous_servarr.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc),
|
||||
(DEFAULT_KEYBINDINGS.help, DEFAULT_KEYBINDINGS.help.desc),
|
||||
];
|
||||
|
||||
pub static BARE_POPUP_CONTEXT_CLUES: [ContextClue; 1] =
|
||||
|
||||
+209
-149
@@ -1,212 +1,272 @@
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use crate::app::context_clues::{
|
||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||
DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
||||
SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
ServarrContextClueProvider,
|
||||
};
|
||||
use crate::app::{context_clues::build_context_clue_string, key_binding::DEFAULT_KEYBINDINGS};
|
||||
|
||||
#[test]
|
||||
fn test_build_context_clue_string() {
|
||||
let test_context_clues_array = [
|
||||
(DEFAULT_KEYBINDINGS.add, "add"),
|
||||
(DEFAULT_KEYBINDINGS.delete, "delete"),
|
||||
];
|
||||
|
||||
assert_str_eq!(
|
||||
build_context_clue_string(&test_context_clues_array),
|
||||
"<a> add | <del> delete"
|
||||
);
|
||||
}
|
||||
use crate::app::{App, key_binding::DEFAULT_KEYBINDINGS};
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
|
||||
#[test]
|
||||
fn test_servarr_context_clues() {
|
||||
let mut servarr_context_clues_iter = SERVARR_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.next_servarr);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.next_servarr.desc);
|
||||
|
||||
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.previous_servarr);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.previous_servarr.desc);
|
||||
|
||||
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.quit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.quit.desc);
|
||||
assert_eq!(servarr_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.up, "scroll up")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.down, "scroll down")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.left, "previous tab")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.right, "next tab")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.pg_up, DEFAULT_KEYBINDINGS.pg_up.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.pg_down,
|
||||
DEFAULT_KEYBINDINGS.pg_down.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.next_servarr,
|
||||
DEFAULT_KEYBINDINGS.next_servarr.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.previous_servarr,
|
||||
DEFAULT_KEYBINDINGS.previous_servarr.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
servarr_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.help, DEFAULT_KEYBINDINGS.help.desc)
|
||||
);
|
||||
assert_none!(servarr_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bare_popup_context_clues() {
|
||||
let mut bare_popup_context_clues_iter = BARE_POPUP_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = bare_popup_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
||||
assert_eq!(bare_popup_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
bare_popup_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_none!(bare_popup_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_downloads_context_clues() {
|
||||
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
|
||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, "update downloads");
|
||||
assert_eq!(downloads_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
downloads_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
downloads_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
downloads_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, "update downloads")
|
||||
);
|
||||
assert_none!(downloads_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocklist_context_clues() {
|
||||
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
|
||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear);
|
||||
assert_str_eq!(*description, "clear blocklist");
|
||||
assert_eq!(blocklist_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
blocklist_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
blocklist_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
blocklist_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
blocklist_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
blocklist_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.clear, "clear blocklist")
|
||||
);
|
||||
assert_none!(blocklist_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confirmation_prompt_context_clues() {
|
||||
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.confirm);
|
||||
assert_str_eq!(*description, "submit");
|
||||
|
||||
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, "cancel");
|
||||
assert_eq!(confirmation_prompt_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
confirmation_prompt_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.confirm, "submit")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
confirmation_prompt_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "cancel")
|
||||
);
|
||||
assert_none!(confirmation_prompt_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_folders_context_clues() {
|
||||
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
||||
|
||||
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
|
||||
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
assert_eq!(root_folders_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
root_folders_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
root_folders_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
root_folders_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_none!(root_folders_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexers_context_clues() {
|
||||
let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "edit indexer");
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.settings);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.settings.desc);
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test);
|
||||
assert_str_eq!(*description, "test indexer");
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test_all);
|
||||
assert_str_eq!(*description, "test all indexers");
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
assert_eq!(indexers_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
indexers_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "edit indexer")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
indexers_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.settings,
|
||||
DEFAULT_KEYBINDINGS.settings.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
indexers_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
indexers_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.test, "test indexer")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
indexers_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.test_all, "test all indexers")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
indexers_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_none!(indexers_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_context_clues() {
|
||||
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
||||
assert_some_eq_x!(
|
||||
system_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.tasks, "open tasks")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
system_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.events, "open events")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
system_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.logs, "open logs")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
system_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, "open updates")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
system_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_none!(system_context_clues_iter.next());
|
||||
}
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.tasks);
|
||||
assert_str_eq!(*description, "open tasks");
|
||||
#[test]
|
||||
fn test_servarr_context_clue_provider_delegates_to_radarr_provider() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
|
||||
|
||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
||||
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.events);
|
||||
assert_str_eq!(*description, "open events");
|
||||
assert_some_eq_x!(
|
||||
context_clues,
|
||||
&crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
);
|
||||
}
|
||||
|
||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
||||
#[test]
|
||||
fn test_servarr_context_clue_provider_delegates_to_sonarr_provider() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.logs);
|
||||
assert_str_eq!(*description, "open logs");
|
||||
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
||||
assert_some_eq_x!(
|
||||
context_clues,
|
||||
&crate::app::sonarr::sonarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, "open updates");
|
||||
#[test]
|
||||
fn test_servarr_context_clue_provider_unsupported_route_returns_none() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveKeybindingBlock::Help.into());
|
||||
|
||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
||||
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
assert_eq!(system_context_clues_iter.next(), None);
|
||||
assert_none!(context_clues);
|
||||
}
|
||||
}
|
||||
|
||||
+79
-1
@@ -14,6 +14,8 @@ generate_keybindings! {
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
pg_down,
|
||||
pg_up,
|
||||
backspace,
|
||||
next_servarr,
|
||||
previous_servarr,
|
||||
@@ -21,6 +23,7 @@ generate_keybindings! {
|
||||
search,
|
||||
auto_search,
|
||||
settings,
|
||||
help,
|
||||
filter,
|
||||
sort,
|
||||
edit,
|
||||
@@ -44,128 +47,203 @@ generate_keybindings! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub struct KeyBinding {
|
||||
pub key: Key,
|
||||
pub alt: Option<Key>,
|
||||
pub desc: &'static str,
|
||||
}
|
||||
|
||||
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||
add: KeyBinding {
|
||||
key: Key::Char('a'),
|
||||
alt: None,
|
||||
desc: "add",
|
||||
},
|
||||
up: KeyBinding {
|
||||
key: Key::Up,
|
||||
alt: Some(Key::Char('k')),
|
||||
desc: "up",
|
||||
},
|
||||
down: KeyBinding {
|
||||
key: Key::Down,
|
||||
alt: Some(Key::Char('j')),
|
||||
desc: "down",
|
||||
},
|
||||
left: KeyBinding {
|
||||
key: Key::Left,
|
||||
alt: Some(Key::Char('h')),
|
||||
desc: "left",
|
||||
},
|
||||
right: KeyBinding {
|
||||
key: Key::Right,
|
||||
alt: Some(Key::Char('l')),
|
||||
desc: "right",
|
||||
},
|
||||
pg_down: KeyBinding {
|
||||
key: Key::PgDown,
|
||||
alt: Some(Key::Ctrl('d')),
|
||||
desc: "page down",
|
||||
},
|
||||
pg_up: KeyBinding {
|
||||
key: Key::PgUp,
|
||||
alt: Some(Key::Ctrl('u')),
|
||||
desc: "page up",
|
||||
},
|
||||
backspace: KeyBinding {
|
||||
key: Key::Backspace,
|
||||
alt: Some(Key::Ctrl('h')),
|
||||
desc: "backspace",
|
||||
},
|
||||
next_servarr: KeyBinding {
|
||||
key: Key::Tab,
|
||||
alt: None,
|
||||
desc: "next servarr",
|
||||
},
|
||||
previous_servarr: KeyBinding {
|
||||
key: Key::BackTab,
|
||||
alt: None,
|
||||
desc: "previous servarr",
|
||||
},
|
||||
clear: KeyBinding {
|
||||
key: Key::Char('c'),
|
||||
alt: None,
|
||||
desc: "clear",
|
||||
},
|
||||
auto_search: KeyBinding {
|
||||
key: Key::Char('S'),
|
||||
alt: None,
|
||||
desc: "auto search",
|
||||
},
|
||||
search: KeyBinding {
|
||||
key: Key::Char('s'),
|
||||
alt: None,
|
||||
desc: "search",
|
||||
},
|
||||
settings: KeyBinding {
|
||||
key: Key::Char('S'),
|
||||
alt: None,
|
||||
desc: "settings",
|
||||
},
|
||||
help: KeyBinding {
|
||||
key: Key::Char('?'),
|
||||
alt: None,
|
||||
desc: "show/hide keybindings",
|
||||
},
|
||||
filter: KeyBinding {
|
||||
key: Key::Char('f'),
|
||||
alt: None,
|
||||
desc: "filter",
|
||||
},
|
||||
sort: KeyBinding {
|
||||
key: Key::Char('o'),
|
||||
alt: None,
|
||||
desc: "sort",
|
||||
},
|
||||
edit: KeyBinding {
|
||||
key: Key::Char('e'),
|
||||
alt: None,
|
||||
desc: "edit",
|
||||
},
|
||||
events: KeyBinding {
|
||||
key: Key::Char('e'),
|
||||
alt: None,
|
||||
desc: "events",
|
||||
},
|
||||
logs: KeyBinding {
|
||||
key: Key::Char('l'),
|
||||
key: Key::Char('L'),
|
||||
alt: None,
|
||||
desc: "logs",
|
||||
},
|
||||
tasks: KeyBinding {
|
||||
key: Key::Char('t'),
|
||||
alt: None,
|
||||
desc: "tasks",
|
||||
},
|
||||
test: KeyBinding {
|
||||
key: Key::Char('t'),
|
||||
alt: None,
|
||||
desc: "test",
|
||||
},
|
||||
test_all: KeyBinding {
|
||||
key: Key::Char('T'),
|
||||
alt: None,
|
||||
desc: "test all",
|
||||
},
|
||||
toggle_monitoring: KeyBinding {
|
||||
key: Key::Char('m'),
|
||||
alt: None,
|
||||
desc: "toggle monitoring",
|
||||
},
|
||||
refresh: KeyBinding {
|
||||
key: Key::Ctrl('r'),
|
||||
alt: None,
|
||||
desc: "refresh",
|
||||
},
|
||||
update: KeyBinding {
|
||||
key: Key::Char('u'),
|
||||
alt: None,
|
||||
desc: "update",
|
||||
},
|
||||
home: KeyBinding {
|
||||
key: Key::Home,
|
||||
alt: None,
|
||||
desc: "home",
|
||||
},
|
||||
end: KeyBinding {
|
||||
key: Key::End,
|
||||
alt: None,
|
||||
desc: "end",
|
||||
},
|
||||
delete: KeyBinding {
|
||||
key: Key::Delete,
|
||||
alt: None,
|
||||
desc: "delete",
|
||||
},
|
||||
submit: KeyBinding {
|
||||
key: Key::Enter,
|
||||
alt: None,
|
||||
desc: "submit",
|
||||
},
|
||||
confirm: KeyBinding {
|
||||
key: Key::Ctrl('s'),
|
||||
alt: None,
|
||||
desc: "submit",
|
||||
},
|
||||
quit: KeyBinding {
|
||||
key: Key::Char('q'),
|
||||
alt: None,
|
||||
desc: "quit",
|
||||
},
|
||||
esc: KeyBinding {
|
||||
key: Key::Esc,
|
||||
alt: None,
|
||||
desc: "close",
|
||||
},
|
||||
};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! matches_key {
|
||||
($binding:ident, $key:expr) => {
|
||||
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|
||||
|| ($crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.is_some()
|
||||
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.unwrap()
|
||||
== $key)
|
||||
};
|
||||
($binding:ident, $key:expr, $ignore_special_keys:expr) => {
|
||||
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|
||||
|| !$ignore_special_keys
|
||||
&& ($crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.is_some()
|
||||
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||
.$binding
|
||||
.alt
|
||||
.unwrap()
|
||||
== $key)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,46 +3,90 @@ mod test {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS};
|
||||
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
|
||||
use crate::event::Key;
|
||||
use crate::matches_key;
|
||||
|
||||
#[rstest]
|
||||
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), "add")]
|
||||
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, "up")]
|
||||
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, "down")]
|
||||
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
|
||||
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
|
||||
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
|
||||
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, "next servarr")]
|
||||
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, "previous servarr")]
|
||||
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")]
|
||||
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), "auto search")]
|
||||
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
|
||||
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), "settings")]
|
||||
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
|
||||
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")]
|
||||
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")]
|
||||
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")]
|
||||
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")]
|
||||
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
|
||||
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
|
||||
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), "toggle monitoring")]
|
||||
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")]
|
||||
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")]
|
||||
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")]
|
||||
#[case(DEFAULT_KEYBINDINGS.end, Key::End, "end")]
|
||||
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")]
|
||||
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), "quit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, "close")]
|
||||
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), None, "add")]
|
||||
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, Some(Key::Char('k')), "up")]
|
||||
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, Some(Key::Char('j')), "down")]
|
||||
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, Some(Key::Char('h')), "left")]
|
||||
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, Some(Key::Char('l')), "right")]
|
||||
#[case(DEFAULT_KEYBINDINGS.pg_down, Key::PgDown, Some(Key::Ctrl('d')), "page down")]
|
||||
#[case(DEFAULT_KEYBINDINGS.pg_up, Key::PgUp, Some(Key::Ctrl('u')), "page up")]
|
||||
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, Some(Key::Ctrl('h')), "backspace")]
|
||||
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, None, "next servarr")]
|
||||
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, None, "previous servarr")]
|
||||
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), None, "clear")]
|
||||
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), None, "auto search")]
|
||||
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), None, "search")]
|
||||
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), None, "settings")]
|
||||
#[case(DEFAULT_KEYBINDINGS.help, Key::Char('?'), None, "show/hide keybindings")]
|
||||
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), None, "filter")]
|
||||
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), None, "sort")]
|
||||
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), None, "edit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), None, "events")]
|
||||
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('L'), None, "logs")]
|
||||
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), None, "tasks")]
|
||||
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), None, "test")]
|
||||
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), None, "test all")]
|
||||
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), None, "toggle monitoring")]
|
||||
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), None, "refresh")]
|
||||
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), None, "update")]
|
||||
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, None, "home")]
|
||||
#[case(DEFAULT_KEYBINDINGS.end, Key::End, None, "end")]
|
||||
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, None, "delete")]
|
||||
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, None, "submit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), None, "submit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), None, "quit")]
|
||||
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, None, "close")]
|
||||
fn test_default_key_bindings_and_descriptions(
|
||||
#[case] key_binding: KeyBinding,
|
||||
#[case] expected_key: Key,
|
||||
#[case] expected_alt_key: Option<Key>,
|
||||
#[case] expected_desc: &str,
|
||||
) {
|
||||
assert_eq!(key_binding.key, expected_key);
|
||||
assert_eq!(key_binding.alt, expected_alt_key);
|
||||
assert_str_eq!(key_binding.desc, expected_desc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_key_macro() {
|
||||
let key = Key::Char('t');
|
||||
|
||||
assert!(matches_key!(test, key));
|
||||
assert!(!matches_key!(test, Key::Char('T')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_key_macro_with_alt_keybinding() {
|
||||
let alt_key = Key::Char('k');
|
||||
let key = Key::Up;
|
||||
|
||||
assert!(matches_key!(up, key));
|
||||
assert!(matches_key!(up, alt_key));
|
||||
assert!(!matches_key!(up, Key::Char('t')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_key_macro_with_alt_keybinding_uses_alt_key_when_ignore_special_keys_is_false() {
|
||||
let alt_key = Key::Char('k');
|
||||
let key = Key::Up;
|
||||
|
||||
assert!(matches_key!(up, key, false));
|
||||
assert!(matches_key!(up, alt_key, false));
|
||||
assert!(!matches_key!(up, Key::Char('t'), false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matches_key_macro_with_alt_keybinding_ignores_alt_key_when_ignore_special_keys_is_true() {
|
||||
let alt_key = Key::Char('k');
|
||||
let key = Key::Up;
|
||||
|
||||
assert!(matches_key!(up, key, true));
|
||||
assert!(!matches_key!(up, alt_key, true));
|
||||
assert!(!matches_key!(up, Key::Char('t'), true));
|
||||
}
|
||||
}
|
||||
|
||||
+145
-56
@@ -1,24 +1,27 @@
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{Error, Result, anyhow};
|
||||
use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error};
|
||||
use regex::Regex;
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, process};
|
||||
use tachyonfx::{Duration, EffectManager};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use veil::Redact;
|
||||
|
||||
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
|
||||
use crate::cli::Command;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||
use crate::models::servarr_models::KeybindingItem;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::{HorizontallyScrollableText, Route, TabRoute, TabState};
|
||||
use crate::network::NetworkEvent;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "app_tests.rs"]
|
||||
mod app_tests;
|
||||
pub mod context_clues;
|
||||
pub mod key_binding;
|
||||
@@ -32,14 +35,19 @@ pub struct App<'a> {
|
||||
pub cancellation_token: CancellationToken,
|
||||
pub is_first_render: bool,
|
||||
pub server_tabs: TabState,
|
||||
pub keymapping_table: Option<StatefulTable<KeybindingItem>>,
|
||||
pub error: HorizontallyScrollableText,
|
||||
pub tick_until_poll: u64,
|
||||
pub ticks_until_scroll: u64,
|
||||
pub tick_count: u64,
|
||||
pub ui_scroll_tick_count: u64,
|
||||
pub last_tick: Duration,
|
||||
pub effects: EffectManager<()>,
|
||||
pub has_active_effect: bool,
|
||||
pub is_routing: bool,
|
||||
pub is_loading: bool,
|
||||
pub should_refresh: bool,
|
||||
pub should_ignore_quit_key: bool,
|
||||
pub ignore_special_keys_for_textbox_input: bool,
|
||||
pub cli_mode: bool,
|
||||
pub data: Data<'a>,
|
||||
}
|
||||
@@ -51,50 +59,45 @@ impl App<'_> {
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Self {
|
||||
let mut server_tabs = Vec::new();
|
||||
let help = format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
);
|
||||
|
||||
if let Some(radarr_configs) = config.radarr {
|
||||
let mut idx = 0;
|
||||
for radarr_config in radarr_configs {
|
||||
let mut unnamed_idx = 0;
|
||||
let radarr_tabs = radarr_configs.into_iter().map(|radarr_config| {
|
||||
let name = if let Some(name) = radarr_config.name.clone() {
|
||||
name
|
||||
} else {
|
||||
idx += 1;
|
||||
format!("Radarr {}", idx)
|
||||
unnamed_idx += 1;
|
||||
format!("Radarr {unnamed_idx}")
|
||||
};
|
||||
|
||||
server_tabs.push(TabRoute {
|
||||
TabRoute {
|
||||
title: name,
|
||||
route: ActiveRadarrBlock::Movies.into(),
|
||||
help: help.clone(),
|
||||
contextual_help: None,
|
||||
config: Some(radarr_config),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
server_tabs.extend(radarr_tabs);
|
||||
}
|
||||
|
||||
if let Some(sonarr_configs) = config.sonarr {
|
||||
let mut idx = 0;
|
||||
|
||||
for sonarr_config in sonarr_configs {
|
||||
let mut unnamed_idx = 0;
|
||||
let sonarr_tabs = sonarr_configs.into_iter().map(|sonarr_config| {
|
||||
let name = if let Some(name) = sonarr_config.name.clone() {
|
||||
name
|
||||
} else {
|
||||
idx += 1;
|
||||
format!("Sonarr {}", idx)
|
||||
unnamed_idx += 1;
|
||||
format!("Sonarr {unnamed_idx}")
|
||||
};
|
||||
|
||||
server_tabs.push(TabRoute {
|
||||
TabRoute {
|
||||
title: name,
|
||||
route: ActiveSonarrBlock::Series.into(),
|
||||
help: help.clone(),
|
||||
contextual_help: None,
|
||||
config: Some(sonarr_config),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
server_tabs.extend(sonarr_tabs);
|
||||
}
|
||||
|
||||
let weight_sorted_tabs = server_tabs
|
||||
@@ -134,12 +137,12 @@ impl App<'_> {
|
||||
self.is_loading = true;
|
||||
}
|
||||
|
||||
if let Some(network_tx) = &self.network_tx {
|
||||
if let Err(e) = network_tx.send(action).await {
|
||||
self.is_loading = false;
|
||||
error!("Failed to send event. {e:?}");
|
||||
self.handle_error(anyhow!(e));
|
||||
}
|
||||
if let Some(network_tx) = &self.network_tx
|
||||
&& let Err(e) = network_tx.send(action).await
|
||||
{
|
||||
self.is_loading = false;
|
||||
error!("Failed to send event. {e:?}");
|
||||
self.handle_error(anyhow!(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +150,14 @@ impl App<'_> {
|
||||
self.tick_count = 0;
|
||||
}
|
||||
|
||||
pub fn on_ui_scroll_tick(&mut self) {
|
||||
if self.ui_scroll_tick_count == self.ticks_until_scroll {
|
||||
self.ui_scroll_tick_count = 0;
|
||||
} else {
|
||||
self.ui_scroll_tick_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.reset_tick_count();
|
||||
@@ -162,7 +173,10 @@ impl App<'_> {
|
||||
}
|
||||
|
||||
pub async fn on_tick(&mut self) {
|
||||
if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh {
|
||||
if self.tick_count.is_multiple_of(self.tick_until_poll)
|
||||
|| self.is_routing
|
||||
|| self.should_refresh
|
||||
{
|
||||
match self.get_current_route() {
|
||||
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
|
||||
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
|
||||
@@ -179,6 +193,7 @@ impl App<'_> {
|
||||
pub fn push_navigation_stack(&mut self, route: Route) {
|
||||
self.navigation_stack.push(route);
|
||||
self.is_routing = true;
|
||||
self.has_active_effect = false;
|
||||
}
|
||||
|
||||
pub fn pop_navigation_stack(&mut self) {
|
||||
@@ -202,10 +217,14 @@ impl App<'_> {
|
||||
}
|
||||
|
||||
pub fn get_current_route(&self) -> Route {
|
||||
*self
|
||||
.navigation_stack
|
||||
.last()
|
||||
.unwrap_or(&self.server_tabs.tabs.first().unwrap().route)
|
||||
*self.navigation_stack.last().unwrap_or(
|
||||
&self
|
||||
.server_tabs
|
||||
.tabs
|
||||
.first()
|
||||
.expect("At least one server tab must exist")
|
||||
.route,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,16 +234,21 @@ impl Default for App<'_> {
|
||||
navigation_stack: Vec::new(),
|
||||
network_tx: None,
|
||||
cancellation_token: CancellationToken::new(),
|
||||
keymapping_table: None,
|
||||
error: HorizontallyScrollableText::default(),
|
||||
is_first_render: true,
|
||||
server_tabs: TabState::new(Vec::new()),
|
||||
tick_until_poll: 400,
|
||||
ticks_until_scroll: 4,
|
||||
tick_count: 0,
|
||||
ui_scroll_tick_count: 0,
|
||||
last_tick: Duration::ZERO,
|
||||
effects: EffectManager::default(),
|
||||
has_active_effect: false,
|
||||
is_loading: false,
|
||||
is_routing: false,
|
||||
should_refresh: false,
|
||||
should_ignore_quit_key: false,
|
||||
ignore_special_keys_for_textbox_input: false,
|
||||
cli_mode: false,
|
||||
data: Data::default(),
|
||||
}
|
||||
@@ -239,20 +263,36 @@ impl App<'_> {
|
||||
TabRoute {
|
||||
title: "Radarr".to_owned(),
|
||||
route: ActiveRadarrBlock::Movies.into(),
|
||||
help: format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
),
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Sonarr".to_owned(),
|
||||
route: ActiveSonarrBlock::Series.into(),
|
||||
help: format!(
|
||||
"<↑↓> scroll | ←→ change tab | {} ",
|
||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
||||
),
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
]),
|
||||
|
||||
..App::default()
|
||||
}
|
||||
}
|
||||
pub fn test_default_fully_populated() -> Self {
|
||||
App {
|
||||
data: Data {
|
||||
radarr_data: RadarrData::test_default_fully_populated(),
|
||||
sonarr_data: SonarrData::test_default_fully_populated(),
|
||||
},
|
||||
server_tabs: TabState::new(vec![
|
||||
TabRoute {
|
||||
title: "Radarr".to_owned(),
|
||||
route: ActiveRadarrBlock::Movies.into(),
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
TabRoute {
|
||||
title: "Sonarr".to_owned(),
|
||||
route: ActiveSonarrBlock::Series.into(),
|
||||
contextual_help: None,
|
||||
config: Some(ServarrConfig::default()),
|
||||
},
|
||||
@@ -270,6 +310,7 @@ pub struct Data<'a> {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
pub struct AppConfig {
|
||||
pub theme: Option<String>,
|
||||
pub radarr: Option<Vec<ServarrConfig>>,
|
||||
pub sonarr: Option<Vec<ServarrConfig>>,
|
||||
}
|
||||
@@ -295,8 +336,7 @@ impl AppConfig {
|
||||
pub fn verify_config_present_for_cli(&self, command: &Command) {
|
||||
let msg = |servarr: &str| {
|
||||
log_and_print_error(format!(
|
||||
"{} configuration missing; Unable to run any {} commands.",
|
||||
servarr, servarr
|
||||
"{servarr} configuration missing; Unable to run any {servarr} commands."
|
||||
))
|
||||
};
|
||||
match command {
|
||||
@@ -346,6 +386,12 @@ pub struct ServarrConfig {
|
||||
pub api_token_file: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||
pub ssl_cert_path: Option<String>,
|
||||
#[serde(
|
||||
default,
|
||||
deserialize_with = "deserialize_optional_env_var_header_map",
|
||||
serialize_with = "serialize_header_map"
|
||||
)]
|
||||
pub custom_headers: Option<HeaderMap>,
|
||||
}
|
||||
|
||||
impl ServarrConfig {
|
||||
@@ -367,8 +413,7 @@ impl ServarrConfig {
|
||||
if let Some(api_token_file) = self.api_token_file.as_ref() {
|
||||
if !PathBuf::from(api_token_file).exists() {
|
||||
log_and_print_error(format!(
|
||||
"The specified {} API token file does not exist",
|
||||
api_token_file
|
||||
"The specified {api_token_file} API token file does not exist"
|
||||
));
|
||||
process::exit(1);
|
||||
}
|
||||
@@ -392,15 +437,37 @@ impl Default for ServarrConfig {
|
||||
api_token: Some(String::new()),
|
||||
api_token_file: None,
|
||||
ssl_cert_path: None,
|
||||
custom_headers: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_and_print_error(error: String) {
|
||||
error!("{}", error);
|
||||
error!("{error}");
|
||||
eprintln!("error: {}", error.red());
|
||||
}
|
||||
|
||||
fn serialize_header_map<S>(headers: &Option<HeaderMap>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
if let Some(headers) = headers {
|
||||
let mut map = HashMap::new();
|
||||
for (name, value) in headers.iter() {
|
||||
let name_str = name.as_str().to_string();
|
||||
let value_str = value
|
||||
.to_str()
|
||||
.map_err(serde::ser::Error::custom)?
|
||||
.to_string();
|
||||
|
||||
map.insert(name_str, value_str);
|
||||
}
|
||||
map.serialize(serializer)
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_optional_env_var<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
@@ -415,6 +482,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_optional_env_var_header_map<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<HeaderMap>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let opt: Option<HashMap<String, String>> = Option::deserialize(deserializer)?;
|
||||
match opt {
|
||||
Some(map) => {
|
||||
let mut header_map = HeaderMap::new();
|
||||
for (k, v) in map.iter() {
|
||||
let name = HeaderName::from_bytes(k.as_bytes()).map_err(serde::de::Error::custom)?;
|
||||
let value_str = interpolate_env_vars(v);
|
||||
let value = HeaderValue::from_str(&value_str).map_err(serde::de::Error::custom)?;
|
||||
header_map.insert(name, value);
|
||||
}
|
||||
Ok(Some(header_map))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_u16_env_var<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
@@ -434,15 +523,15 @@ where
|
||||
|
||||
fn interpolate_env_vars(s: &str) -> String {
|
||||
let result = s.to_string();
|
||||
let scrubbing_regex = Regex::new(r#"[\s\{\}!\$^\(\)\[\]\\\|`'"]+"#).unwrap();
|
||||
let var_regex = Regex::new(r"\$\{(.*?)\}").unwrap();
|
||||
let scrubbing_regex = Regex::new(r#"[\s{}!$^()\[\]\\|`'"]+"#).unwrap();
|
||||
let var_regex = Regex::new(r"\$\{(.*?)}").unwrap();
|
||||
|
||||
var_regex
|
||||
.replace_all(s, |caps: ®ex::Captures<'_>| {
|
||||
if let Some(mat) = caps.get(1) {
|
||||
if let Ok(value) = std::env::var(mat.as_str()) {
|
||||
return scrubbing_regex.replace_all(&value, "").to_string();
|
||||
}
|
||||
if let Some(mat) = caps.get(1)
|
||||
&& let Ok(value) = std::env::var(mat.as_str())
|
||||
{
|
||||
return scrubbing_regex.replace_all(&value, "").to_string();
|
||||
}
|
||||
|
||||
scrubbing_regex.replace_all(&result, "").to_string()
|
||||
|
||||
@@ -40,7 +40,7 @@ impl App<'_> {
|
||||
}
|
||||
ActiveRadarrBlock::Downloads => {
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
|
||||
.await;
|
||||
}
|
||||
ActiveRadarrBlock::RootFolders => {
|
||||
@@ -59,7 +59,7 @@ impl App<'_> {
|
||||
.dispatch_network_event(RadarrEvent::GetMovies.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
|
||||
.await;
|
||||
}
|
||||
ActiveRadarrBlock::Indexers => {
|
||||
@@ -185,7 +185,7 @@ impl App<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.tick_count % self.tick_until_poll == 0 {
|
||||
if self.tick_count.is_multiple_of(self.tick_until_poll) {
|
||||
self.refresh_radarr_metadata().await;
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,7 @@ impl App<'_> {
|
||||
.dispatch_network_event(RadarrEvent::GetRootFolders.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
||||
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(RadarrEvent::GetDiskSpace.into())
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
use crate::app::context_clues::ContextClue;
|
||||
use crate::app::App;
|
||||
use crate::app::context_clues::{
|
||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||
};
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ADD_MOVIE_BLOCKS, ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS, EDIT_INDEXER_BLOCKS,
|
||||
EDIT_MOVIE_BLOCKS, INDEXER_SETTINGS_BLOCKS, MOVIE_DETAILS_BLOCKS,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "radarr_context_clues_tests.rs"]
|
||||
mod radarr_context_clues_tests;
|
||||
|
||||
pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||
pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 11] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
@@ -49,7 +61,7 @@ pub static MOVIE_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
@@ -61,12 +73,10 @@ pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 1] =
|
||||
[(DEFAULT_KEYBINDINGS.submit, "details")];
|
||||
|
||||
pub static ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||
@@ -82,3 +92,54 @@ pub static COLLECTION_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
(DEFAULT_KEYBINDINGS.edit, "edit collection"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub(in crate::app) struct RadarrContextClueProvider;
|
||||
|
||||
impl ContextClueProvider for RadarrContextClueProvider {
|
||||
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||
let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() else {
|
||||
panic!("RadarrContextClueProvider::get_context_clues called with non-Radarr route");
|
||||
};
|
||||
match active_radarr_block {
|
||||
_ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_info_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
ActiveRadarrBlock::TestAllIndexers
|
||||
| ActiveRadarrBlock::AddMovieSearchInput
|
||||
| ActiveRadarrBlock::AddMovieEmptySearchResults
|
||||
| ActiveRadarrBlock::SystemLogs
|
||||
| ActiveRadarrBlock::SystemUpdates => Some(&BARE_POPUP_CONTEXT_CLUES),
|
||||
_ if context_option.unwrap_or(active_radarr_block)
|
||||
== ActiveRadarrBlock::ViewMovieOverview =>
|
||||
{
|
||||
Some(&BARE_POPUP_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveRadarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||
_ if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block)
|
||||
|| EDIT_INDEXER_BLOCKS.contains(&active_radarr_block)
|
||||
|| INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block)
|
||||
|| EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) =>
|
||||
{
|
||||
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveRadarrBlock::AddMoviePrompt
|
||||
| ActiveRadarrBlock::AddMovieSelectMonitor
|
||||
| ActiveRadarrBlock::AddMovieSelectMinimumAvailability
|
||||
| ActiveRadarrBlock::AddMovieSelectQualityProfile
|
||||
| ActiveRadarrBlock::AddMovieSelectRootFolder
|
||||
| ActiveRadarrBlock::AddMovieTagsInput
|
||||
| ActiveRadarrBlock::SystemTaskStartConfirmPrompt => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||
_ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => {
|
||||
Some(&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveRadarrBlock::CollectionDetails => Some(&COLLECTION_DETAILS_CONTEXT_CLUES),
|
||||
_ => app
|
||||
.data
|
||||
.radarr_data
|
||||
.main_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +1,118 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::context_clues::{
|
||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES,
|
||||
COLLECTION_DETAILS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES,
|
||||
MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
|
||||
MOVIE_DETAILS_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTION_DETAILS_CONTEXT_CLUES,
|
||||
COLLECTIONS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
|
||||
MOVIE_DETAILS_CONTEXT_CLUES, RadarrContextClueProvider, SYSTEM_TASKS_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_library_context_clues() {
|
||||
let mut library_context_clues_iter = LIBRARY_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, "update all");
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
|
||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, "cancel filter");
|
||||
assert_eq!(library_context_clues_iter.next(), None);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
library_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||
);
|
||||
assert_none!(library_context_clues_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collections_context_clues() {
|
||||
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, "update all");
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
|
||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, "cancel filter");
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collections_context_clues.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||
);
|
||||
assert_eq!(collections_context_clues.next(), None);
|
||||
}
|
||||
|
||||
@@ -116,30 +120,32 @@ mod tests {
|
||||
fn test_movie_details_context_clues() {
|
||||
let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
||||
|
||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
||||
|
||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
||||
|
||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
||||
assert_some_eq_x!(
|
||||
movie_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
movie_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
movie_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
movie_details_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
movie_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_eq!(movie_details_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
@@ -147,53 +153,41 @@ mod tests {
|
||||
fn test_manual_movie_search_context_clues() {
|
||||
let mut manual_movie_search_context_clues_iter = MANUAL_MOVIE_SEARCH_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
||||
|
||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
||||
|
||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
||||
|
||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
||||
|
||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
||||
|
||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
||||
assert_eq!(manual_movie_search_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manual_movie_search_contextual_context_clues() {
|
||||
let mut manual_movie_search_contextual_context_clues_iter =
|
||||
MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = manual_movie_search_contextual_context_clues_iter
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
assert_eq!(
|
||||
manual_movie_search_contextual_context_clues_iter.next(),
|
||||
None
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
manual_movie_search_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_eq!(manual_movie_search_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -201,15 +195,14 @@ mod tests {
|
||||
let mut add_movie_search_results_context_clues_iter =
|
||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = add_movie_search_results_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "details");
|
||||
|
||||
let (key_binding, description) = add_movie_search_results_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, "edit search");
|
||||
assert_some_eq_x!(
|
||||
add_movie_search_results_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
add_movie_search_results_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, "edit search")
|
||||
);
|
||||
assert_eq!(add_movie_search_results_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
@@ -217,15 +210,14 @@ mod tests {
|
||||
fn test_system_tasks_context_clues() {
|
||||
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "start task");
|
||||
|
||||
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
||||
assert_some_eq_x!(
|
||||
system_tasks_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "start task")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
system_tasks_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_eq!(system_tasks_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
@@ -233,20 +225,255 @@ mod tests {
|
||||
fn test_collection_details_context_clues() {
|
||||
let mut collection_details_context_clues_iter = COLLECTION_DETAILS_CONTEXT_CLUES.iter();
|
||||
|
||||
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "show overview/add movie");
|
||||
|
||||
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, "edit collection");
|
||||
|
||||
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
||||
assert_some_eq_x!(
|
||||
collection_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.submit, "show overview/add movie")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collection_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.edit, "edit collection")
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
collection_details_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||
);
|
||||
assert_eq!(collection_details_context_clues_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "RadarrContextClueProvider::get_context_clues called with non-Radarr route"
|
||||
)]
|
||||
fn test_radarr_context_clue_provider_get_context_clues_non_radarr_route() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveSonarrBlock::default().into());
|
||||
|
||||
// This should panic because the route is not a Radarr route
|
||||
RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(ActiveRadarrBlock::TestAllIndexers, None)]
|
||||
#[case(ActiveRadarrBlock::AddMovieSearchInput, None)]
|
||||
#[case(ActiveRadarrBlock::AddMovieEmptySearchResults, None)]
|
||||
#[case(ActiveRadarrBlock::SystemLogs, None)]
|
||||
#[case(ActiveRadarrBlock::SystemUpdates, None)]
|
||||
#[case(ActiveRadarrBlock::ViewMovieOverview, None)]
|
||||
#[case(
|
||||
ActiveRadarrBlock::CollectionDetails,
|
||||
Some(ActiveRadarrBlock::ViewMovieOverview)
|
||||
)]
|
||||
fn test_radarr_context_clue_provider_bare_popup_context_clues(
|
||||
#[case] active_radarr_block: ActiveRadarrBlock,
|
||||
#[case] context_option: Option<ActiveRadarrBlock>,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack((active_radarr_block, context_option).into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveRadarrBlock::MovieDetails, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(1, ActiveRadarrBlock::MovieHistory, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(2, ActiveRadarrBlock::FileInfo, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(3, ActiveRadarrBlock::Cast, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(4, ActiveRadarrBlock::Crew, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||
#[case(5, ActiveRadarrBlock::ManualSearch, &MANUAL_MOVIE_SEARCH_CONTEXT_CLUES)]
|
||||
fn test_radarr_context_clue_provider_movie_details_block_context_clues(
|
||||
#[case] index: usize,
|
||||
#[case] active_radarr_block: ActiveRadarrBlock,
|
||||
#[case] expected_context_clues: &[ContextClue],
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.data.radarr_data.movie_info_tabs.set_index(index);
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues(
|
||||
#[values(
|
||||
ActiveRadarrBlock::AddMoviePrompt,
|
||||
ActiveRadarrBlock::AddMovieSelectMonitor,
|
||||
ActiveRadarrBlock::AddMovieSelectMinimumAvailability,
|
||||
ActiveRadarrBlock::AddMovieSelectQualityProfile,
|
||||
ActiveRadarrBlock::AddMovieSelectRootFolder,
|
||||
ActiveRadarrBlock::AddMovieTagsInput,
|
||||
ActiveRadarrBlock::SystemTaskStartConfirmPrompt
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_edit_collection_blocks(
|
||||
#[values(
|
||||
ActiveRadarrBlock::EditCollectionPrompt,
|
||||
ActiveRadarrBlock::EditCollectionConfirmPrompt,
|
||||
ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
|
||||
ActiveRadarrBlock::EditCollectionSelectQualityProfile,
|
||||
ActiveRadarrBlock::EditCollectionToggleSearchOnAdd,
|
||||
ActiveRadarrBlock::EditCollectionToggleMonitored
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_edit_indexer_blocks(
|
||||
#[values(
|
||||
ActiveRadarrBlock::EditIndexerPrompt,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveRadarrBlock::EditIndexerPriorityInput,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_indexer_settings_blocks(
|
||||
#[values(
|
||||
ActiveRadarrBlock::AllIndexerSettingsPrompt,
|
||||
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||
ActiveRadarrBlock::IndexerSettingsConfirmPrompt,
|
||||
ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||
ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
||||
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||
ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
|
||||
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags,
|
||||
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_edit_movie_blocks(
|
||||
#[values(
|
||||
ActiveRadarrBlock::EditMoviePrompt,
|
||||
ActiveRadarrBlock::EditMovieConfirmPrompt,
|
||||
ActiveRadarrBlock::EditMoviePathInput,
|
||||
ActiveRadarrBlock::EditMovieSelectMinimumAvailability,
|
||||
ActiveRadarrBlock::EditMovieSelectQualityProfile,
|
||||
ActiveRadarrBlock::EditMovieTagsInput,
|
||||
ActiveRadarrBlock::EditMovieToggleMonitored
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_radarr_context_clue_provider_add_movie_search_results_context_clues(
|
||||
#[values(
|
||||
ActiveRadarrBlock::AddMovieSearchResults,
|
||||
ActiveRadarrBlock::AddMovieAlreadyInLibrary
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_context_clue_provider_collection_details_context_clues() {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &COLLECTION_DETAILS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_context_clue_provider_system_tasks_context_clues() {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
|
||||
app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(0, ActiveRadarrBlock::Movies, &LIBRARY_CONTEXT_CLUES)]
|
||||
#[case(1, ActiveRadarrBlock::Collections, &COLLECTIONS_CONTEXT_CLUES)]
|
||||
#[case(2, ActiveRadarrBlock::Downloads, &DOWNLOADS_CONTEXT_CLUES)]
|
||||
#[case(3, ActiveRadarrBlock::Blocklist, &BLOCKLIST_CONTEXT_CLUES)]
|
||||
#[case(4, ActiveRadarrBlock::RootFolders, &ROOT_FOLDERS_CONTEXT_CLUES)]
|
||||
#[case(5, ActiveRadarrBlock::Indexers, &INDEXERS_CONTEXT_CLUES)]
|
||||
#[case(6, ActiveRadarrBlock::System, &SYSTEM_CONTEXT_CLUES)]
|
||||
fn test_radarr_context_clue_provider_radarr_blocks_context_clues(
|
||||
#[case] index: usize,
|
||||
#[case] active_radarr_block: ActiveRadarrBlock,
|
||||
#[case] expected_context_clues: &[ContextClue],
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.data.radarr_data.main_tabs.set_index(index);
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
|
||||
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||
|
||||
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::app::radarr::ActiveRadarrBlock;
|
||||
use crate::app::App;
|
||||
use crate::app::radarr::ActiveRadarrBlock;
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieBody, AddMovieOptions, Collection, CollectionMovie, Credit, Movie, RadarrRelease,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
||||
use crate::models::servarr_models::Indexer;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::NetworkEvent;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_dispatch_by_blocklist_block() {
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
assert!(app.is_loading);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
@@ -591,7 +591,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
@@ -625,7 +625,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
@@ -650,7 +650,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
@@ -675,7 +675,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(app.should_refresh);
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
@@ -692,7 +692,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
assert!(app.should_refresh);
|
||||
@@ -722,7 +722,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
RadarrEvent::GetDownloads.into()
|
||||
RadarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
}
|
||||
|
||||
+13
-14
@@ -53,7 +53,7 @@ impl App<'_> {
|
||||
)
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
||||
.dispatch_network_event(SonarrEvent::GetDownloads(500).into())
|
||||
.await;
|
||||
}
|
||||
ActiveSonarrBlock::SeasonHistory => {
|
||||
@@ -94,21 +94,20 @@ impl App<'_> {
|
||||
.await;
|
||||
}
|
||||
ActiveSonarrBlock::ManualEpisodeSearch => {
|
||||
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref() {
|
||||
if let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() {
|
||||
if episode_details_modal.episode_releases.is_empty() {
|
||||
self
|
||||
.dispatch_network_event(
|
||||
SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref()
|
||||
&& let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref()
|
||||
&& episode_details_modal.episode_releases.is_empty()
|
||||
{
|
||||
self
|
||||
.dispatch_network_event(
|
||||
SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
ActiveSonarrBlock::Downloads => {
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
||||
.dispatch_network_event(SonarrEvent::GetDownloads(500).into())
|
||||
.await;
|
||||
}
|
||||
ActiveSonarrBlock::Blocklist => {
|
||||
@@ -215,7 +214,7 @@ impl App<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if self.tick_count % self.tick_until_poll == 0 {
|
||||
if self.tick_count.is_multiple_of(self.tick_until_poll) {
|
||||
self.refresh_sonarr_metadata().await;
|
||||
}
|
||||
}
|
||||
@@ -234,7 +233,7 @@ impl App<'_> {
|
||||
.dispatch_network_event(SonarrEvent::GetRootFolders.into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
||||
.dispatch_network_event(SonarrEvent::GetDownloads(500).into())
|
||||
.await;
|
||||
self
|
||||
.dispatch_network_event(SonarrEvent::GetDiskSpace.into())
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
use crate::app::{context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
|
||||
use crate::app::context_clues::{
|
||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClueProvider,
|
||||
};
|
||||
use crate::app::{App, context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ADD_SERIES_BLOCKS, ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, EDIT_SERIES_BLOCKS,
|
||||
EPISODE_DETAILS_BLOCKS, INDEXER_SETTINGS_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sonarr_context_clues_tests.rs"]
|
||||
@@ -9,9 +17,13 @@ pub static ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||
];
|
||||
|
||||
pub static SERIES_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||
pub static SERIES_CONTEXT_CLUES: [ContextClue; 11] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
@@ -75,12 +87,7 @@ pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||
];
|
||||
|
||||
pub static SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.submit, "episode details"),
|
||||
(DEFAULT_KEYBINDINGS.delete, "delete episode"),
|
||||
];
|
||||
|
||||
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
@@ -95,9 +102,11 @@ pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "episode details"),
|
||||
(DEFAULT_KEYBINDINGS.delete, "delete episode"),
|
||||
];
|
||||
|
||||
pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
@@ -109,10 +118,11 @@ pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||
];
|
||||
|
||||
pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
||||
pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
@@ -122,10 +132,11 @@ pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
||||
pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
@@ -135,12 +146,10 @@ pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static DETAILS_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 1] =
|
||||
[(DEFAULT_KEYBINDINGS.submit, "details")];
|
||||
|
||||
pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
@@ -153,7 +162,84 @@ pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 4] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.refresh,
|
||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||
),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.auto_search,
|
||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||
(DEFAULT_KEYBINDINGS.submit, "start task"),
|
||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||
];
|
||||
|
||||
pub(in crate::app) struct SonarrContextClueProvider;
|
||||
|
||||
impl ContextClueProvider for SonarrContextClueProvider {
|
||||
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||
let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() else {
|
||||
panic!("SonarrContextClueProvider::get_context_clues called with non-Sonarr route");
|
||||
};
|
||||
match active_sonarr_block {
|
||||
_ if SERIES_DETAILS_BLOCKS.contains(&active_sonarr_block) => app
|
||||
.data
|
||||
.sonarr_data
|
||||
.series_info_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
_ if SEASON_DETAILS_BLOCKS.contains(&active_sonarr_block) => app
|
||||
.data
|
||||
.sonarr_data
|
||||
.season_details_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.season_details_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
_ if EPISODE_DETAILS_BLOCKS.contains(&active_sonarr_block) => app
|
||||
.data
|
||||
.sonarr_data
|
||||
.season_details_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.episode_details_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.episode_details_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
ActiveSonarrBlock::TestAllIndexers
|
||||
| ActiveSonarrBlock::AddSeriesSearchInput
|
||||
| ActiveSonarrBlock::AddSeriesEmptySearchResults
|
||||
| ActiveSonarrBlock::SystemLogs
|
||||
| ActiveSonarrBlock::SystemUpdates => Some(&BARE_POPUP_CONTEXT_CLUES),
|
||||
_ if EDIT_INDEXER_BLOCKS.contains(&active_sonarr_block)
|
||||
|| INDEXER_SETTINGS_BLOCKS.contains(&active_sonarr_block)
|
||||
|| EDIT_SERIES_BLOCKS.contains(&active_sonarr_block) =>
|
||||
{
|
||||
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveSonarrBlock::AddSeriesPrompt
|
||||
| ActiveSonarrBlock::AddSeriesSelectMonitor
|
||||
| ActiveSonarrBlock::AddSeriesSelectSeriesType
|
||||
| ActiveSonarrBlock::AddSeriesSelectQualityProfile
|
||||
| ActiveSonarrBlock::AddSeriesSelectLanguageProfile
|
||||
| ActiveSonarrBlock::AddSeriesSelectRootFolder
|
||||
| ActiveSonarrBlock::AddSeriesTagsInput
|
||||
| ActiveSonarrBlock::SystemTaskStartConfirmPrompt => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||
_ if ADD_SERIES_BLOCKS.contains(&active_sonarr_block) => {
|
||||
Some(&ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||
}
|
||||
ActiveSonarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||
_ => app
|
||||
.data
|
||||
.sonarr_data
|
||||
.main_tabs
|
||||
.get_active_route_contextual_help(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ mod tests {
|
||||
},
|
||||
sonarr_models::{Season, Series, SonarrRelease},
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, NetworkEvent},
|
||||
network::{NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -107,7 +107,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
@@ -366,7 +366,7 @@ mod tests {
|
||||
assert!(app.is_loading);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||
assert_eq!(app.tick_count, 0);
|
||||
@@ -604,7 +604,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
@@ -642,7 +642,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
@@ -667,7 +667,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||
}
|
||||
@@ -692,7 +692,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(app.should_refresh);
|
||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||
@@ -709,7 +709,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
assert!(app.should_refresh);
|
||||
@@ -743,7 +743,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
sync_network_rx.recv().await.unwrap(),
|
||||
SonarrEvent::GetDownloads.into()
|
||||
SonarrEvent::GetDownloads(500).into()
|
||||
);
|
||||
assert!(app.is_loading);
|
||||
}
|
||||
|
||||
+12
-12
@@ -2,16 +2,18 @@
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{error::ErrorKind, CommandFactory};
|
||||
use clap::{CommandFactory, error::ErrorKind};
|
||||
use mockall::predicate::eq;
|
||||
use rstest::rstest;
|
||||
use serde_json::json;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
Cli,
|
||||
app::App,
|
||||
cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
|
||||
models::{
|
||||
Serdeable,
|
||||
radarr_models::{
|
||||
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
||||
RadarrSerdeable,
|
||||
@@ -20,12 +22,10 @@ mod tests {
|
||||
BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
|
||||
SonarrSerdeable,
|
||||
},
|
||||
Serdeable,
|
||||
},
|
||||
network::{
|
||||
radarr_network::RadarrEvent, sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent,
|
||||
MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent, sonarr_network::SonarrEvent,
|
||||
},
|
||||
Cli,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -33,7 +33,7 @@ mod tests {
|
||||
fn test_servarr_subcommand_requires_subcommand(#[values("radarr", "sonarr")] subcommand: &str) {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", subcommand]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
||||
@@ -45,21 +45,21 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sonarr_subcommand_delegates_to_sonarr() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completions_requires_argument() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "completions"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
||||
@@ -70,7 +70,7 @@ mod tests {
|
||||
fn test_completions_invalid_argument() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "completions", "test"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ mod tests {
|
||||
fn test_completions_satisfied_with_argument() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "completions", "bash"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -141,7 +141,7 @@ mod tests {
|
||||
|
||||
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -172,6 +172,6 @@ mod tests {
|
||||
|
||||
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Subcommand};
|
||||
use clap::{Subcommand, command};
|
||||
use clap_complete::Shell;
|
||||
use radarr::{RadarrCliHandler, RadarrCommand};
|
||||
use sonarr::{SonarrCliHandler, SonarrCommand};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{arg, command, ArgAction, Subcommand};
|
||||
use clap::{ArgAction, Subcommand, arg, command};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::RadarrCommand;
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
|
||||
use crate::{
|
||||
Cli,
|
||||
cli::{
|
||||
radarr::{add_command_handler::RadarrAddCommand, RadarrCommand},
|
||||
Command,
|
||||
radarr::{RadarrCommand, add_command_handler::RadarrAddCommand},
|
||||
},
|
||||
models::radarr_models::{MinimumAvailability, MovieMonitor},
|
||||
Cli,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -32,7 +32,7 @@ mod tests {
|
||||
fn test_add_movie_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "movie"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -52,7 +52,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
"/test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -92,7 +92,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -117,7 +117,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -156,7 +156,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -207,10 +207,11 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
assert_ok!(&result);
|
||||
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type")
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -243,10 +244,11 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
assert_ok!(&result);
|
||||
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type")
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -285,10 +287,11 @@ mod tests {
|
||||
"--no-search-for-movie",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
assert_ok!(&result);
|
||||
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type")
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -296,7 +299,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "add", "root-folder"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -318,18 +321,19 @@ mod tests {
|
||||
"/nfs/test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type")
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_tag_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "tag"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -344,11 +348,12 @@ mod tests {
|
||||
|
||||
let result = Cli::try_parse_from(["managarr", "radarr", "add", "tag", "--name", "test"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type")
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,12 +362,12 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{radarr::add_command_handler::RadarrAddCommandHandler, CliCommandHandler},
|
||||
cli::{CliCommandHandler, radarr::add_command_handler::RadarrAddCommandHandler},
|
||||
models::{
|
||||
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
|
||||
Serdeable,
|
||||
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
|
||||
},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@@ -416,7 +421,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -447,7 +452,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -474,7 +479,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::radarr_models::DeleteMovieParams,
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
cli::{
|
||||
radarr::{delete_command_handler::RadarrDeleteCommand, RadarrCommand},
|
||||
Command,
|
||||
},
|
||||
Cli,
|
||||
cli::{
|
||||
Command,
|
||||
radarr::{RadarrCommand, delete_command_handler::RadarrDeleteCommand},
|
||||
},
|
||||
};
|
||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -30,7 +30,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "blocklist-item"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -52,12 +52,13 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -65,7 +66,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "download"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -85,19 +86,20 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_indexer_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "indexer"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -117,19 +119,20 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_movie_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "movie"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -147,12 +150,13 @@ mod tests {
|
||||
let result =
|
||||
Cli::try_parse_from(["managarr", "radarr", "delete", "movie", "--movie-id", "1"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -174,12 +178,13 @@ mod tests {
|
||||
"--add-list-exclusion",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -187,7 +192,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "root-folder"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -207,19 +212,20 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_tag_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "tag"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -232,12 +238,13 @@ mod tests {
|
||||
|
||||
let result = Cli::try_parse_from(["managarr", "radarr", "delete", "tag", "--tag-id", "1"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,14 +258,14 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
radarr::delete_command_handler::{RadarrDeleteCommand, RadarrDeleteCommandHandler},
|
||||
CliCommandHandler,
|
||||
radarr::delete_command_handler::{RadarrDeleteCommand, RadarrDeleteCommandHandler},
|
||||
},
|
||||
models::{
|
||||
radarr_models::{DeleteMovieParams, RadarrSerdeable},
|
||||
Serdeable,
|
||||
radarr_models::{DeleteMovieParams, RadarrSerdeable},
|
||||
},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -289,7 +296,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -315,7 +322,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -341,7 +348,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -375,7 +382,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -401,7 +408,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -427,7 +434,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{mutex_flags_or_default, mutex_flags_or_option, CliCommandHandler, Command},
|
||||
cli::{CliCommandHandler, Command, mutex_flags_or_default, mutex_flags_or_option},
|
||||
models::{
|
||||
Serdeable,
|
||||
radarr_models::{
|
||||
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, RadarrSerdeable,
|
||||
},
|
||||
servarr_models::EditIndexerParams,
|
||||
Serdeable,
|
||||
},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
cli::{
|
||||
radarr::{edit_command_handler::RadarrEditCommand, RadarrCommand},
|
||||
Command,
|
||||
},
|
||||
Cli,
|
||||
cli::{
|
||||
Command,
|
||||
radarr::{RadarrCommand, edit_command_handler::RadarrEditCommand},
|
||||
},
|
||||
};
|
||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -42,7 +42,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "all-indexer-settings"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -60,7 +60,7 @@ mod tests {
|
||||
"--disable-allow-hardcoded-subs",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ mod tests {
|
||||
"--disable-prefer-indexer-flags",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -126,11 +126,12 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -168,11 +169,12 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -180,7 +182,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "collection"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -198,7 +200,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -218,7 +220,7 @@ mod tests {
|
||||
"--disable-monitoring",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -235,7 +237,7 @@ mod tests {
|
||||
"--disable-search-on-add",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -252,7 +254,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -270,7 +272,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -298,11 +300,12 @@ mod tests {
|
||||
"/test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -334,18 +337,19 @@ mod tests {
|
||||
"--search-on-add",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "indexer"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -363,7 +367,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -383,7 +387,7 @@ mod tests {
|
||||
"--disable-rss",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -400,7 +404,7 @@ mod tests {
|
||||
"--disable-automatic-search",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -417,7 +421,7 @@ mod tests {
|
||||
"--disable-interactive-search",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -435,7 +439,7 @@ mod tests {
|
||||
"--clear-tags",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -453,7 +457,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -487,11 +491,12 @@ mod tests {
|
||||
"Test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -526,11 +531,12 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -578,18 +584,19 @@ mod tests {
|
||||
"25",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_movie_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "movie"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -607,7 +614,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -627,7 +634,7 @@ mod tests {
|
||||
"--disable-monitoring",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -645,7 +652,7 @@ mod tests {
|
||||
"--clear-tags",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -669,7 +676,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -686,7 +693,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -714,11 +721,12 @@ mod tests {
|
||||
"/nfs/test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -747,11 +755,12 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -787,11 +796,12 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,18 +815,18 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
radarr::edit_command_handler::{RadarrEditCommand, RadarrEditCommandHandler},
|
||||
CliCommandHandler,
|
||||
radarr::edit_command_handler::{RadarrEditCommand, RadarrEditCommandHandler},
|
||||
},
|
||||
models::{
|
||||
Serdeable,
|
||||
radarr_models::{
|
||||
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability,
|
||||
RadarrSerdeable,
|
||||
},
|
||||
servarr_models::EditIndexerParams,
|
||||
Serdeable,
|
||||
},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -887,7 +897,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -958,12 +968,12 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_all_indexer_settings_command_unprovided_values_default_to_previous_values(
|
||||
) {
|
||||
async fn test_handle_edit_all_indexer_settings_command_unprovided_values_default_to_previous_values()
|
||||
{
|
||||
let expected_edit_all_indexer_settings = IndexerSettings {
|
||||
allow_hardcoded_subs: true,
|
||||
availability_delay: 2,
|
||||
@@ -1030,7 +1040,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1072,7 +1082,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1114,7 +1124,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1156,7 +1166,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1210,7 +1220,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1264,7 +1274,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1318,7 +1328,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1361,7 +1371,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1404,7 +1414,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1447,7 +1457,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Subcommand};
|
||||
use clap::{Subcommand, command};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::error::ErrorKind;
|
||||
use clap::CommandFactory;
|
||||
use clap::error::ErrorKind;
|
||||
|
||||
use crate::cli::radarr::get_command_handler::RadarrGetCommand;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::Command;
|
||||
use crate::Cli;
|
||||
use crate::cli::Command;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::radarr::get_command_handler::RadarrGetCommand;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -27,7 +27,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -35,7 +35,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "host-config"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -43,7 +43,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-details"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -61,7 +61,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -69,7 +69,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-history"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -95,7 +95,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "security-config"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -103,7 +103,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "system-status"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,11 +117,11 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
radarr::get_command_handler::{RadarrGetCommand, RadarrGetCommandHandler},
|
||||
CliCommandHandler,
|
||||
radarr::get_command_handler::{RadarrGetCommand, RadarrGetCommandHandler},
|
||||
},
|
||||
models::{radarr_models::RadarrSerdeable, Serdeable},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
models::{Serdeable, radarr_models::RadarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -149,7 +149,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -172,7 +172,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -198,7 +198,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -224,7 +224,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -247,7 +247,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -270,7 +270,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Subcommand};
|
||||
use clap::{Subcommand, command};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
@@ -23,7 +23,10 @@ pub enum RadarrListCommand {
|
||||
#[command(about = "List all Radarr collections")]
|
||||
Collections,
|
||||
#[command(about = "List all active downloads in Radarr")]
|
||||
Downloads,
|
||||
Downloads {
|
||||
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
|
||||
count: u64,
|
||||
},
|
||||
#[command(about = "List disk space details for all provisioned root folders in Radarr")]
|
||||
DiskSpace,
|
||||
#[command(about = "List all Radarr indexers")]
|
||||
@@ -104,10 +107,10 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
RadarrListCommand::Downloads => {
|
||||
RadarrListCommand::Downloads { count } => {
|
||||
let resp = self
|
||||
.network
|
||||
.handle_network_event(RadarrEvent::GetDownloads.into())
|
||||
.handle_network_event(RadarrEvent::GetDownloads(count).into())
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::error::ErrorKind;
|
||||
use clap::CommandFactory;
|
||||
use clap::error::ErrorKind;
|
||||
|
||||
use crate::cli::radarr::list_command_handler::RadarrListCommand;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::Command;
|
||||
use crate::Cli;
|
||||
use crate::cli::Command;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::radarr::list_command_handler::RadarrListCommand;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -29,7 +29,6 @@ mod tests {
|
||||
#[values(
|
||||
"blocklist",
|
||||
"collections",
|
||||
"downloads",
|
||||
"disk-space",
|
||||
"indexers",
|
||||
"movies",
|
||||
@@ -44,7 +43,7 @@ mod tests {
|
||||
) {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "list", subcommand]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -52,19 +51,28 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "movie-credits"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_downloads_count_flag_requires_arguments() {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "downloads", "--count"]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_logs_events_flag_requires_arguments() {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "logs", "--events"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -80,11 +88,27 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::List(credits_command))) = result.unwrap().command {
|
||||
assert_eq!(credits_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::List(credits_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(credits_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_downloads_default_values() {
|
||||
let expected_args = RadarrListCommand::Downloads { count: 500 };
|
||||
let result = Cli::try_parse_from(["managarr", "radarr", "list", "downloads"]);
|
||||
|
||||
assert_ok!(&result);
|
||||
|
||||
let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -95,11 +119,13 @@ mod tests {
|
||||
};
|
||||
let result = Cli::try_parse_from(["managarr", "radarr", "list", "logs"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command {
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,14 +141,13 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::radarr::list_command_handler::{RadarrListCommand, RadarrListCommandHandler},
|
||||
models::{radarr_models::RadarrSerdeable, Serdeable},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
models::{Serdeable, radarr_models::RadarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[rstest]
|
||||
#[case(RadarrListCommand::Blocklist, RadarrEvent::GetBlocklist)]
|
||||
#[case(RadarrListCommand::Collections, RadarrEvent::GetCollections)]
|
||||
#[case(RadarrListCommand::Downloads, RadarrEvent::GetDownloads)]
|
||||
#[case(RadarrListCommand::DiskSpace, RadarrEvent::GetDiskSpace)]
|
||||
#[case(RadarrListCommand::Indexers, RadarrEvent::GetIndexers)]
|
||||
#[case(RadarrListCommand::Movies, RadarrEvent::GetMovies)]
|
||||
@@ -153,7 +178,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -179,7 +204,33 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_list_downloads_command() {
|
||||
let expected_count = 1000;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
RadarrEvent::GetDownloads(expected_count).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let list_downloads_command = RadarrListCommand::Downloads { count: 1000 };
|
||||
|
||||
let result =
|
||||
RadarrListCommandHandler::with(&app_arc, list_downloads_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -207,7 +258,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -13,8 +13,8 @@ use crate::app::App;
|
||||
|
||||
use crate::cli::CliCommandHandler;
|
||||
use crate::models::radarr_models::{RadarrReleaseDownloadBody, RadarrTaskName};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::NetworkTrait;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use anyhow::Result;
|
||||
|
||||
use super::Command;
|
||||
@@ -118,6 +118,17 @@ pub enum RadarrCommand {
|
||||
},
|
||||
#[command(about = "Test all Radarr indexers")]
|
||||
TestAllIndexers,
|
||||
#[command(
|
||||
about = "Toggle monitoring for the specified movie corresponding to the given movie ID"
|
||||
)]
|
||||
ToggleMovieMonitoring {
|
||||
#[arg(
|
||||
long,
|
||||
help = "The Radarr ID of the movie to toggle monitoring on",
|
||||
required = true
|
||||
)]
|
||||
movie_id: i64,
|
||||
},
|
||||
#[command(about = "Trigger an automatic search for the movie with the specified ID")]
|
||||
TriggerAutomaticSearch {
|
||||
#[arg(
|
||||
@@ -250,6 +261,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
RadarrCommand::ToggleMovieMonitoring { movie_id } => {
|
||||
let resp = self
|
||||
.network
|
||||
.handle_network_event(RadarrEvent::ToggleMovieMonitoring(movie_id).into())
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
RadarrCommand::TriggerAutomaticSearch { movie_id } => {
|
||||
let resp = self
|
||||
.network
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::error::ErrorKind;
|
||||
use clap::CommandFactory;
|
||||
use clap::error::ErrorKind;
|
||||
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::Command;
|
||||
use crate::Cli;
|
||||
use crate::cli::Command;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -28,7 +28,7 @@ mod tests {
|
||||
) {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", subcommand]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -43,7 +43,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -62,7 +62,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -81,7 +81,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -102,14 +102,14 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manual_search_requires_movie_id() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "manual-search"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -126,14 +126,14 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search_new_movie_requires_query() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "search-new-movie"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -150,14 +150,14 @@ mod tests {
|
||||
"halo",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_task_requires_task_name() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "start-task"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -174,7 +174,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -188,14 +188,14 @@ mod tests {
|
||||
"application-check-update",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_indexer_requires_indexer_id() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "test-indexer"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -212,7 +212,32 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_movie_monitoring_requires_movie_id() {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "toggle-movie-monitoring"]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_movie_monitoring_requirements_satisfied() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"radarr",
|
||||
"toggle-movie-monitoring",
|
||||
"--movie-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -220,7 +245,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "trigger-automatic-search"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -237,7 +262,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,22 +276,22 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
radarr::{
|
||||
add_command_handler::RadarrAddCommand, delete_command_handler::RadarrDeleteCommand,
|
||||
edit_command_handler::RadarrEditCommand, get_command_handler::RadarrGetCommand,
|
||||
list_command_handler::RadarrListCommand, refresh_command_handler::RadarrRefreshCommand,
|
||||
RadarrCliHandler, RadarrCommand,
|
||||
},
|
||||
CliCommandHandler,
|
||||
radarr::{
|
||||
RadarrCliHandler, RadarrCommand, add_command_handler::RadarrAddCommand,
|
||||
delete_command_handler::RadarrDeleteCommand, edit_command_handler::RadarrEditCommand,
|
||||
get_command_handler::RadarrGetCommand, list_command_handler::RadarrListCommand,
|
||||
refresh_command_handler::RadarrRefreshCommand,
|
||||
},
|
||||
},
|
||||
models::{
|
||||
Serdeable,
|
||||
radarr_models::{
|
||||
BlocklistItem, BlocklistResponse, IndexerSettings, RadarrReleaseDownloadBody,
|
||||
RadarrSerdeable, RadarrTaskName,
|
||||
},
|
||||
Serdeable,
|
||||
},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -299,7 +324,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -332,7 +357,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -357,7 +382,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -384,7 +409,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -411,7 +436,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -436,7 +461,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -458,7 +483,33 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_toggle_movie_monitoring_command() {
|
||||
let expected_movie_id = 1;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
RadarrEvent::ToggleMovieMonitoring(expected_movie_id).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let toggle_movie_monitoring_command = RadarrCommand::ToggleMovieMonitoring { movie_id: 1 };
|
||||
|
||||
let result =
|
||||
RadarrCliHandler::with(&app_arc, toggle_movie_monitoring_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -487,7 +538,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -514,7 +565,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -543,7 +594,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -615,7 +666,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -644,7 +695,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -670,7 +721,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -696,7 +747,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
use super::RadarrCommand;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::error::ErrorKind;
|
||||
use clap::CommandFactory;
|
||||
use clap::error::ErrorKind;
|
||||
|
||||
use crate::cli::radarr::refresh_command_handler::RadarrRefreshCommand;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::Command;
|
||||
use crate::Cli;
|
||||
use crate::cli::Command;
|
||||
use crate::cli::radarr::RadarrCommand;
|
||||
use crate::cli::radarr::refresh_command_handler::RadarrRefreshCommand;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -31,14 +31,14 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", subcommand]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refresh_movie_requires_movie_id() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", "movie"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -51,13 +51,13 @@ mod tests {
|
||||
let result =
|
||||
Cli::try_parse_from(["managarr", "radarr", "refresh", "movie", "--movie-id", "1"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Radarr(RadarrCommand::Refresh(refresh_command))) =
|
||||
result.unwrap().command
|
||||
{
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
let Some(Command::Radarr(RadarrCommand::Refresh(refresh_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::radarr::refresh_command_handler::{RadarrRefreshCommand, RadarrRefreshCommandHandler},
|
||||
models::{radarr_models::RadarrSerdeable, Serdeable},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
models::{Serdeable, radarr_models::RadarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||
};
|
||||
|
||||
#[rstest]
|
||||
@@ -102,7 +102,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -128,7 +128,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::sonarr_models::{AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
sonarr::{add_command_handler::SonarrAddCommand, SonarrCommand},
|
||||
Command,
|
||||
},
|
||||
Cli,
|
||||
cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, add_command_handler::SonarrAddCommand},
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -34,7 +34,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "root-folder"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -56,18 +56,19 @@ mod tests {
|
||||
"/nfs/test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "series"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -91,7 +92,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -115,7 +116,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -139,7 +140,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -163,7 +164,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -187,7 +188,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -216,7 +217,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -239,7 +240,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -263,7 +264,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -288,7 +289,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -325,10 +326,11 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
assert_ok!(&result);
|
||||
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -368,10 +370,11 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
assert_ok!(&result);
|
||||
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -418,17 +421,18 @@ mod tests {
|
||||
"--no-search-for-series",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
assert_ok!(&result);
|
||||
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_tag_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "tag"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -443,11 +447,12 @@ mod tests {
|
||||
|
||||
let result = Cli::try_parse_from(["managarr", "sonarr", "add", "tag", "--name", "test"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,14 +461,14 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{sonarr::add_command_handler::SonarrAddCommandHandler, CliCommandHandler},
|
||||
cli::{CliCommandHandler, sonarr::add_command_handler::SonarrAddCommandHandler},
|
||||
models::{
|
||||
Serdeable,
|
||||
sonarr_models::{
|
||||
AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType, SonarrSerdeable,
|
||||
},
|
||||
Serdeable,
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@@ -501,7 +506,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -554,7 +559,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -581,7 +586,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::sonarr_models::DeleteSeriesParams,
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
cli::{
|
||||
sonarr::{delete_command_handler::SonarrDeleteCommand, SonarrCommand},
|
||||
Command,
|
||||
},
|
||||
Cli,
|
||||
cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, delete_command_handler::SonarrDeleteCommand},
|
||||
},
|
||||
};
|
||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -30,7 +30,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "blocklist-item"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -52,12 +52,13 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -65,7 +66,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "download"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -85,12 +86,13 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -98,7 +100,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "episode-file"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -118,19 +120,20 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_indexer_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "indexer"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -150,12 +153,13 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -163,7 +167,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "root-folder"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -183,19 +187,20 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_series_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "series"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -213,12 +218,13 @@ mod tests {
|
||||
let result =
|
||||
Cli::try_parse_from(["managarr", "sonarr", "delete", "series", "--series-id", "1"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -240,19 +246,20 @@ mod tests {
|
||||
"--add-list-exclusion",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_tag_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "tag"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -265,12 +272,13 @@ mod tests {
|
||||
|
||||
let result = Cli::try_parse_from(["managarr", "sonarr", "delete", "tag", "--tag-id", "1"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(delete_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,14 +292,14 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
sonarr::delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler},
|
||||
CliCommandHandler,
|
||||
sonarr::delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler},
|
||||
},
|
||||
models::{
|
||||
sonarr_models::{DeleteSeriesParams, SonarrSerdeable},
|
||||
Serdeable,
|
||||
sonarr_models::{DeleteSeriesParams, SonarrSerdeable},
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -322,7 +330,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -348,7 +356,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -374,7 +382,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -400,7 +408,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -426,7 +434,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -460,7 +468,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -486,7 +494,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::sonarr_models::SonarrReleaseDownloadBody,
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
cli::{
|
||||
sonarr::{download_command_handler::SonarrDownloadCommand, SonarrCommand},
|
||||
Command,
|
||||
},
|
||||
Cli,
|
||||
cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, download_command_handler::SonarrDownloadCommand},
|
||||
},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -41,7 +41,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -61,7 +61,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -81,7 +81,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -103,7 +103,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -121,7 +121,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -143,7 +143,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -165,7 +165,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -187,7 +187,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -211,7 +211,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -227,7 +227,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -247,7 +247,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -267,7 +267,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -289,7 +289,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,14 +303,14 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
sonarr::download_command_handler::{SonarrDownloadCommand, SonarrDownloadCommandHandler},
|
||||
CliCommandHandler,
|
||||
sonarr::download_command_handler::{SonarrDownloadCommand, SonarrDownloadCommandHandler},
|
||||
},
|
||||
models::{
|
||||
sonarr_models::{SonarrReleaseDownloadBody, SonarrSerdeable},
|
||||
Serdeable,
|
||||
sonarr_models::{SonarrReleaseDownloadBody, SonarrSerdeable},
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -345,7 +345,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -382,7 +382,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -417,7 +417,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{mutex_flags_or_option, CliCommandHandler, Command},
|
||||
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
||||
models::{
|
||||
Serdeable,
|
||||
servarr_models::EditIndexerParams,
|
||||
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
|
||||
Serdeable,
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
sonarr::{edit_command_handler::SonarrEditCommand, SonarrCommand},
|
||||
Command,
|
||||
sonarr::{SonarrCommand, edit_command_handler::SonarrEditCommand},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -20,10 +20,10 @@ mod tests {
|
||||
}
|
||||
|
||||
mod cli {
|
||||
use crate::{models::sonarr_models::SeriesType, Cli};
|
||||
use crate::{Cli, models::sonarr_models::SeriesType};
|
||||
|
||||
use super::*;
|
||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
||||
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -32,7 +32,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "all-indexer-settings"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -57,7 +57,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -78,11 +78,12 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -108,18 +109,19 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "indexer"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -137,7 +139,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -157,7 +159,7 @@ mod tests {
|
||||
"--disable-rss",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -174,7 +176,7 @@ mod tests {
|
||||
"--disable-automatic-search",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -191,7 +193,7 @@ mod tests {
|
||||
"--disable-interactive-search",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -209,7 +211,7 @@ mod tests {
|
||||
"--clear-tags",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -227,7 +229,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -261,11 +263,12 @@ mod tests {
|
||||
"Test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -300,11 +303,12 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -352,18 +356,19 @@ mod tests {
|
||||
"25",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_series_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "series"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -381,7 +386,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -401,7 +406,7 @@ mod tests {
|
||||
"--disable-monitoring",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -418,7 +423,7 @@ mod tests {
|
||||
"--disable-season-folders",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -436,7 +441,7 @@ mod tests {
|
||||
"--clear-tags",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
@@ -461,7 +466,7 @@ mod tests {
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -478,7 +483,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -509,11 +514,12 @@ mod tests {
|
||||
"/nfs/test",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -545,11 +551,12 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -591,11 +598,12 @@ mod tests {
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(edit_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,15 +617,15 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
sonarr::edit_command_handler::{SonarrEditCommand, SonarrEditCommandHandler},
|
||||
CliCommandHandler,
|
||||
sonarr::edit_command_handler::{SonarrEditCommand, SonarrEditCommandHandler},
|
||||
},
|
||||
models::{
|
||||
Serdeable,
|
||||
servarr_models::EditIndexerParams,
|
||||
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
|
||||
Serdeable,
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -674,7 +682,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -728,7 +736,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -776,7 +784,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -824,7 +832,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -872,7 +880,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
sonarr::{get_command_handler::SonarrGetCommand, SonarrCommand},
|
||||
Command,
|
||||
};
|
||||
use crate::Cli;
|
||||
use crate::cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, get_command_handler::SonarrGetCommand},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -28,7 +28,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "all-indexer-settings"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -36,7 +36,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "system-status"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -44,7 +44,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "episode-details"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -62,7 +62,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -70,7 +70,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "host-config"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -78,7 +78,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "security-config"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -86,7 +86,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "series-details"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -104,7 +104,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,11 +118,11 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
sonarr::get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler},
|
||||
CliCommandHandler,
|
||||
sonarr::get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler},
|
||||
},
|
||||
models::{sonarr_models::SonarrSerdeable, Serdeable},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
models::{Serdeable, sonarr_models::SonarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -150,7 +150,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -199,7 +199,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -222,7 +222,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -248,7 +248,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -271,7 +271,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
@@ -21,7 +21,10 @@ pub enum SonarrListCommand {
|
||||
#[command(about = "List all items in the Sonarr blocklist")]
|
||||
Blocklist,
|
||||
#[command(about = "List all active downloads in Sonarr")]
|
||||
Downloads,
|
||||
Downloads {
|
||||
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
|
||||
count: u64,
|
||||
},
|
||||
#[command(about = "List disk space details for all provisioned root folders in Sonarr")]
|
||||
DiskSpace,
|
||||
#[command(about = "List the episodes for the series with the given ID")]
|
||||
@@ -146,10 +149,10 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
SonarrListCommand::Downloads => {
|
||||
SonarrListCommand::Downloads { count } => {
|
||||
let resp = self
|
||||
.network
|
||||
.handle_network_event(SonarrEvent::GetDownloads.into())
|
||||
.handle_network_event(SonarrEvent::GetDownloads(count).into())
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
sonarr::{list_command_handler::SonarrListCommand, SonarrCommand},
|
||||
Command,
|
||||
};
|
||||
use crate::Cli;
|
||||
use crate::cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, list_command_handler::SonarrListCommand},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -19,7 +19,7 @@ mod tests {
|
||||
|
||||
mod cli {
|
||||
use super::*;
|
||||
use clap::{error::ErrorKind, Parser};
|
||||
use clap::{Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -28,7 +28,6 @@ mod tests {
|
||||
#[values(
|
||||
"blocklist",
|
||||
"series",
|
||||
"downloads",
|
||||
"disk-space",
|
||||
"quality-profiles",
|
||||
"indexers",
|
||||
@@ -43,14 +42,14 @@ mod tests {
|
||||
) {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", subcommand]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_episodes_requires_series_id() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episodes"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -62,7 +61,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-files"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -74,7 +73,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-history"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -93,13 +92,37 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::List(episode_history_command))) =
|
||||
let Some(Command::Sonarr(SonarrCommand::List(episode_history_command))) =
|
||||
result.unwrap().command
|
||||
{
|
||||
assert_eq!(episode_history_command, expected_args);
|
||||
}
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(episode_history_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_downloads_count_flag_requires_arguments() {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "downloads", "--count"]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_downloads_default_values() {
|
||||
let expected_args = SonarrListCommand::Downloads { count: 500 };
|
||||
let result = Cli::try_parse_from(["managarr", "sonarr", "list", "downloads"]);
|
||||
|
||||
assert_ok!(&result);
|
||||
|
||||
let Some(Command::Sonarr(SonarrCommand::List(downloads_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(downloads_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -107,7 +130,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "history", "--events"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -116,11 +139,13 @@ mod tests {
|
||||
let expected_args = SonarrListCommand::History { events: 500 };
|
||||
let result = Cli::try_parse_from(["managarr", "sonarr", "list", "history"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::List(history_command))) = result.unwrap().command {
|
||||
assert_eq!(history_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::List(history_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(history_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -128,7 +153,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "logs", "--events"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -140,11 +165,12 @@ mod tests {
|
||||
};
|
||||
let result = Cli::try_parse_from(["managarr", "sonarr", "list", "logs"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::List(logs_command))) = result.unwrap().command {
|
||||
assert_eq!(logs_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::List(logs_command))) = result.unwrap().command else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(logs_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -153,12 +179,13 @@ mod tests {
|
||||
let result =
|
||||
Cli::try_parse_from(["managarr", "sonarr", "list", "episodes", "--series-id", "1"]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::List(episodes_command))) = result.unwrap().command
|
||||
{
|
||||
assert_eq!(episodes_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::List(episodes_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(episodes_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -173,13 +200,14 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::List(episode_files_command))) =
|
||||
let Some(Command::Sonarr(SonarrCommand::List(episode_files_command))) =
|
||||
result.unwrap().command
|
||||
{
|
||||
assert_eq!(episode_files_command, expected_args);
|
||||
}
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(episode_files_command, expected_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -193,7 +221,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -211,7 +239,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -231,7 +259,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -239,7 +267,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series-history"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -258,11 +286,13 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::List(series_command))) = result.unwrap().command {
|
||||
assert_eq!(series_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::List(series_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(series_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,10 +305,10 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::cli::sonarr::list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
|
||||
use crate::cli::CliCommandHandler;
|
||||
use crate::models::sonarr_models::SonarrSerdeable;
|
||||
use crate::cli::sonarr::list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
|
||||
use crate::models::Serdeable;
|
||||
use crate::models::sonarr_models::SonarrSerdeable;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::{
|
||||
app::App,
|
||||
@@ -287,7 +317,6 @@ mod tests {
|
||||
|
||||
#[rstest]
|
||||
#[case(SonarrListCommand::Blocklist, SonarrEvent::GetBlocklist)]
|
||||
#[case(SonarrListCommand::Downloads, SonarrEvent::GetDownloads)]
|
||||
#[case(SonarrListCommand::DiskSpace, SonarrEvent::GetDiskSpace)]
|
||||
#[case(SonarrListCommand::Indexers, SonarrEvent::GetIndexers)]
|
||||
#[case(SonarrListCommand::QualityProfiles, SonarrEvent::GetQualityProfiles)]
|
||||
@@ -319,7 +348,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -345,7 +374,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -371,7 +400,33 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_list_downloads_command() {
|
||||
let expected_count = 1000;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
SonarrEvent::GetDownloads(expected_count).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let list_downloads_command = SonarrListCommand::Downloads { count: 1000 };
|
||||
|
||||
let result =
|
||||
SonarrListCommandHandler::with(&app_arc, list_downloads_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -397,7 +452,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -425,7 +480,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -451,7 +506,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -477,7 +532,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -507,7 +562,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
sonarr::{manual_search_command_handler::SonarrManualSearchCommand, SonarrCommand},
|
||||
Command,
|
||||
};
|
||||
use crate::Cli;
|
||||
use crate::cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, manual_search_command_handler::SonarrManualSearchCommand},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -36,7 +36,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -54,7 +54,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -74,7 +74,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -82,7 +82,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "manual-search", "episode"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -100,7 +100,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,13 +114,13 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
CliCommandHandler,
|
||||
sonarr::manual_search_command_handler::{
|
||||
SonarrManualSearchCommand, SonarrManualSearchCommandHandler,
|
||||
},
|
||||
CliCommandHandler,
|
||||
},
|
||||
models::{sonarr_models::SonarrSerdeable, Serdeable},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
models::{Serdeable, sonarr_models::SonarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -149,7 +149,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -182,7 +182,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -18,7 +18,7 @@ use trigger_automatic_search_command_handler::{
|
||||
use crate::{
|
||||
app::App,
|
||||
models::sonarr_models::SonarrTaskName,
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::{CliCommandHandler, Command};
|
||||
@@ -146,6 +146,17 @@ pub enum SonarrCommand {
|
||||
)]
|
||||
season_number: i64,
|
||||
},
|
||||
#[command(
|
||||
about = "Toggle monitoring for the specified series corresponding to the given series ID"
|
||||
)]
|
||||
ToggleSeriesMonitoring {
|
||||
#[arg(
|
||||
long,
|
||||
help = "The Sonarr ID of the series to toggle monitoring on",
|
||||
required = true
|
||||
)]
|
||||
series_id: i64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<SonarrCommand> for Command {
|
||||
@@ -290,6 +301,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
SonarrCommand::ToggleSeriesMonitoring { series_id } => {
|
||||
let resp = self
|
||||
.network
|
||||
.handle_network_event(SonarrEvent::ToggleSeriesMonitoring(series_id).into())
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
|
||||
@@ -6,7 +6,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::cli::{
|
||||
sonarr::{refresh_command_handler::SonarrRefreshCommand, SonarrCommand},
|
||||
Command,
|
||||
};
|
||||
use crate::Cli;
|
||||
use crate::cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, refresh_command_handler::SonarrRefreshCommand},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[test]
|
||||
@@ -20,7 +20,7 @@ mod tests {
|
||||
|
||||
mod cli {
|
||||
use super::*;
|
||||
use clap::{error::ErrorKind, Parser};
|
||||
use clap::{Parser, error::ErrorKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -31,14 +31,14 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "refresh", subcommand]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refresh_series_requires_series_id() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "refresh", "series"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -57,13 +57,13 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
|
||||
if let Some(Command::Sonarr(SonarrCommand::Refresh(refresh_command))) =
|
||||
result.unwrap().command
|
||||
{
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
let Some(Command::Sonarr(SonarrCommand::Refresh(refresh_command))) = result.unwrap().command
|
||||
else {
|
||||
panic!("Unexpected command type");
|
||||
};
|
||||
assert_eq!(refresh_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +77,11 @@ mod tests {
|
||||
|
||||
use crate::{app::App, cli::sonarr::refresh_command_handler::SonarrRefreshCommandHandler};
|
||||
use crate::{
|
||||
cli::{sonarr::refresh_command_handler::SonarrRefreshCommand, CliCommandHandler},
|
||||
cli::{CliCommandHandler, sonarr::refresh_command_handler::SonarrRefreshCommand},
|
||||
network::sonarr_network::SonarrEvent,
|
||||
};
|
||||
use crate::{
|
||||
models::{sonarr_models::SonarrSerdeable, Serdeable},
|
||||
models::{Serdeable, sonarr_models::SonarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent},
|
||||
};
|
||||
|
||||
@@ -109,7 +109,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -135,7 +135,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
sonarr::{list_command_handler::SonarrListCommand, SonarrCommand},
|
||||
Command,
|
||||
};
|
||||
use crate::Cli;
|
||||
use crate::cli::{
|
||||
Command,
|
||||
sonarr::{SonarrCommand, list_command_handler::SonarrListCommand},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -29,7 +29,7 @@ mod tests {
|
||||
) {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", subcommand]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -37,7 +37,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "mark-history-item-as-failed"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -54,14 +54,14 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search_new_series_requires_query() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "search-new-series"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -78,14 +78,14 @@ mod tests {
|
||||
"halo",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_task_requires_task_name() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "start-task"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -102,7 +102,7 @@ mod tests {
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
@@ -116,14 +116,14 @@ mod tests {
|
||||
"application-update-check",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_test_indexer_requires_indexer_id() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "test-indexer"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -148,7 +148,7 @@ mod tests {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-episode-monitoring"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -165,7 +165,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -178,7 +178,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -195,7 +195,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -214,7 +214,32 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_series_monitoring_requires_series_id() {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-series-monitoring"]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_series_monitoring_requirements_satisfied() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"toggle-series-monitoring",
|
||||
"--series-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,25 +253,25 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
CliCommandHandler,
|
||||
sonarr::{
|
||||
add_command_handler::SonarrAddCommand, delete_command_handler::SonarrDeleteCommand,
|
||||
SonarrCliHandler, SonarrCommand, add_command_handler::SonarrAddCommand,
|
||||
delete_command_handler::SonarrDeleteCommand,
|
||||
download_command_handler::SonarrDownloadCommand, edit_command_handler::SonarrEditCommand,
|
||||
get_command_handler::SonarrGetCommand, list_command_handler::SonarrListCommand,
|
||||
manual_search_command_handler::SonarrManualSearchCommand,
|
||||
refresh_command_handler::SonarrRefreshCommand,
|
||||
trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand,
|
||||
SonarrCliHandler, SonarrCommand,
|
||||
},
|
||||
CliCommandHandler,
|
||||
},
|
||||
models::{
|
||||
Serdeable,
|
||||
sonarr_models::{
|
||||
BlocklistItem, BlocklistResponse, IndexerSettings, Series, SonarrReleaseDownloadBody,
|
||||
SonarrSerdeable, SonarrTaskName,
|
||||
},
|
||||
Serdeable,
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -279,7 +304,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -309,7 +334,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -336,7 +361,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -365,7 +390,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -401,7 +426,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -459,12 +484,12 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sonarr_cli_handler_delegates_manual_search_commands_to_the_manual_search_command_handler(
|
||||
) {
|
||||
async fn test_sonarr_cli_handler_delegates_manual_search_commands_to_the_manual_search_command_handler()
|
||||
{
|
||||
let expected_episode_id = 1;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
@@ -487,12 +512,12 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sonarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler(
|
||||
) {
|
||||
async fn test_sonarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler()
|
||||
{
|
||||
let expected_episode_id = 1;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
@@ -517,7 +542,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -539,7 +564,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -561,7 +586,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -587,7 +612,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -614,7 +639,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -641,7 +666,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -666,7 +691,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -688,11 +713,11 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_toggle_episode_monitoring_command() {
|
||||
async fn test_toggle_episode_monitoring_command() {
|
||||
let expected_episode_id = 1;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
@@ -718,11 +743,11 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_toggle_season_monitoring_command() {
|
||||
async fn test_toggle_season_monitoring_command() {
|
||||
let expected_series_id = 1;
|
||||
let expected_season_number = 1;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
@@ -751,7 +776,36 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_toggle_series_monitoring_command() {
|
||||
let expected_series_id = 1;
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
SonarrEvent::ToggleSeriesMonitoring(expected_series_id).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let toggle_series_monitoring_command = SonarrCommand::ToggleSeriesMonitoring { series_id: 1 };
|
||||
|
||||
let result = SonarrCliHandler::with(
|
||||
&app_arc,
|
||||
toggle_series_monitoring_command,
|
||||
&mut mock_network,
|
||||
)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
use super::SonarrCommand;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
sonarr::{
|
||||
trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand, SonarrCommand,
|
||||
},
|
||||
Command,
|
||||
};
|
||||
use crate::Cli;
|
||||
use crate::cli::{
|
||||
Command,
|
||||
sonarr::{
|
||||
SonarrCommand, trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand,
|
||||
},
|
||||
};
|
||||
use clap::CommandFactory;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -36,7 +36,7 @@ mod tests {
|
||||
"series",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -54,7 +54,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -68,7 +68,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -86,7 +86,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -106,7 +106,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -118,7 +118,7 @@ mod tests {
|
||||
"episode",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
@@ -136,7 +136,7 @@ mod tests {
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,13 +150,13 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{
|
||||
CliCommandHandler,
|
||||
sonarr::trigger_automatic_search_command_handler::{
|
||||
SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler,
|
||||
},
|
||||
CliCommandHandler,
|
||||
},
|
||||
models::{sonarr_models::SonarrSerdeable, Serdeable},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
models::{Serdeable, sonarr_models::SonarrSerdeable},
|
||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -220,7 +220,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -250,7 +250,7 @@ mod tests {
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossterm::event;
|
||||
use crossterm::event::Event as CrosstermEvent;
|
||||
use crossterm::event::{Event as CrosstermEvent, KeyEventKind};
|
||||
|
||||
use crate::event::Key;
|
||||
|
||||
@@ -28,8 +29,12 @@ impl Events {
|
||||
let timeout = tick_rate
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0));
|
||||
if event::poll(timeout).unwrap() {
|
||||
if let CrosstermEvent::Key(key_event) = event::read().unwrap() {
|
||||
if event::poll(timeout).unwrap()
|
||||
&& let CrosstermEvent::Key(key_event) = event::read().unwrap()
|
||||
{
|
||||
// Only process the key event if it's a press event
|
||||
// Source: https://ratatui.rs/faq/ Why am I getting duplicate key events on Windows?
|
||||
if key_event.kind == KeyEventKind::Press {
|
||||
let key = Key::from(key_event);
|
||||
tx.send(InputEvent::KeyEvent(key)).unwrap();
|
||||
}
|
||||
@@ -45,7 +50,10 @@ impl Events {
|
||||
Events { rx }
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<InputEvent<Key>, mpsc::RecvError> {
|
||||
self.rx.recv()
|
||||
pub fn next(&self) -> Result<Option<InputEvent<Key>>> {
|
||||
match self.rx.try_recv() {
|
||||
Ok(event) => Ok(Some(event)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-15
@@ -13,6 +13,8 @@ pub enum Key {
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
PgDown,
|
||||
PgUp,
|
||||
Enter,
|
||||
Esc,
|
||||
Backspace,
|
||||
@@ -29,21 +31,23 @@ pub enum Key {
|
||||
impl Display for Key {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Key::Char(c) => write!(f, "<{c}>"),
|
||||
Key::Ctrl(c) => write!(f, "<ctrl-{c}>"),
|
||||
Key::Up => write!(f, "<↑>"),
|
||||
Key::Down => write!(f, "<↓>"),
|
||||
Key::Left => write!(f, "<←>"),
|
||||
Key::Right => write!(f, "<→>"),
|
||||
Key::Enter => write!(f, "<enter>"),
|
||||
Key::Esc => write!(f, "<esc>"),
|
||||
Key::Backspace => write!(f, "<backspace>"),
|
||||
Key::Home => write!(f, "<home>"),
|
||||
Key::End => write!(f, "<end>"),
|
||||
Key::Tab => write!(f, "<tab>"),
|
||||
Key::BackTab => write!(f, "<shift-tab>"),
|
||||
Key::Delete => write!(f, "<del>"),
|
||||
_ => write!(f, "<{self:?}>"),
|
||||
Key::Char(c) => write!(f, "{c}"),
|
||||
Key::Ctrl(c) => write!(f, "ctrl-{c}"),
|
||||
Key::Up => write!(f, "↑"),
|
||||
Key::Down => write!(f, "↓"),
|
||||
Key::Left => write!(f, "←"),
|
||||
Key::Right => write!(f, "→"),
|
||||
Key::PgDown => write!(f, "pgDown"),
|
||||
Key::PgUp => write!(f, "pgUp"),
|
||||
Key::Enter => write!(f, "enter"),
|
||||
Key::Esc => write!(f, "esc"),
|
||||
Key::Backspace => write!(f, "backspace"),
|
||||
Key::Home => write!(f, "home"),
|
||||
Key::End => write!(f, "end"),
|
||||
Key::Tab => write!(f, "tab"),
|
||||
Key::BackTab => write!(f, "shift-tab"),
|
||||
Key::Delete => write!(f, "del"),
|
||||
_ => write!(f, "{self:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +70,14 @@ impl From<KeyEvent> for Key {
|
||||
code: KeyCode::Right,
|
||||
..
|
||||
} => Key::Right,
|
||||
KeyEvent {
|
||||
code: KeyCode::PageDown,
|
||||
..
|
||||
} => Key::PgDown,
|
||||
KeyEvent {
|
||||
code: KeyCode::PageUp,
|
||||
..
|
||||
} => Key::PgUp,
|
||||
KeyEvent {
|
||||
code: KeyCode::Backspace,
|
||||
..
|
||||
|
||||
+13
-1
@@ -11,6 +11,8 @@ mod tests {
|
||||
#[case(Key::Down, "↓")]
|
||||
#[case(Key::Left, "←")]
|
||||
#[case(Key::Right, "→")]
|
||||
#[case(Key::PgDown, "pgDown")]
|
||||
#[case(Key::PgUp, "pgUp")]
|
||||
#[case(Key::Enter, "enter")]
|
||||
#[case(Key::Esc, "esc")]
|
||||
#[case(Key::Backspace, "backspace")]
|
||||
@@ -22,7 +24,7 @@ mod tests {
|
||||
#[case(Key::Char('q'), "q")]
|
||||
#[case(Key::Ctrl('q'), "ctrl-q")]
|
||||
fn test_key_formatter(#[case] key: Key, #[case] expected_str: &str) {
|
||||
assert_str_eq!(format!("{key}"), format!("<{expected_str}>"));
|
||||
assert_str_eq!(format!("{key}"), format!("{expected_str}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -45,6 +47,16 @@ mod tests {
|
||||
assert_eq!(Key::from(KeyEvent::from(KeyCode::Right)), Key::Right);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_from_page_down() {
|
||||
assert_eq!(Key::from(KeyEvent::from(KeyCode::PageDown)), Key::PgDown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_from_page_up() {
|
||||
assert_eq!(Key::from(KeyEvent::from(KeyCode::PageUp)), Key::PgUp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_from_backspace() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
#[cfg(test)]
|
||||
mod property_tests {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::handlers::handler_test_utils::test_utils::proptest_helpers::*;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::{Scrollable, Paginated};
|
||||
|
||||
proptest! {
|
||||
/// Property test: Table never panics on index selection
|
||||
#[test]
|
||||
fn test_table_index_selection_safety(
|
||||
list_size in list_size(),
|
||||
index in 0usize..1000
|
||||
) {
|
||||
let mut table = StatefulTable::<Movie>::default();
|
||||
let movies: Vec<Movie> = (0..list_size).map(|i| {
|
||||
let mut movie = Movie::default();
|
||||
movie.id = i as i64;
|
||||
movie
|
||||
}).collect();
|
||||
|
||||
table.set_items(movies);
|
||||
|
||||
// Try to select an arbitrary index
|
||||
if index < list_size {
|
||||
table.select_index(Some(index));
|
||||
let selected = table.current_selection();
|
||||
prop_assert_eq!(selected.id, index as i64);
|
||||
} else {
|
||||
// Out of bounds selection should be safe
|
||||
table.select_index(Some(index));
|
||||
// Should not panic, selection stays valid
|
||||
}
|
||||
}
|
||||
|
||||
/// Property test: Table state remains consistent after scroll operations
|
||||
#[test]
|
||||
fn test_table_scroll_consistency(
|
||||
list_size in list_size(),
|
||||
scroll_amount in 0usize..20
|
||||
) {
|
||||
let mut table = StatefulTable::<Movie>::default();
|
||||
let movies: Vec<Movie> = (0..list_size).map(|i| {
|
||||
let mut movie = Movie::default();
|
||||
movie.id = i as i64;
|
||||
movie
|
||||
}).collect();
|
||||
|
||||
table.set_items(movies);
|
||||
let initial_id = table.current_selection().id;
|
||||
|
||||
// Scroll down multiple times
|
||||
for _ in 0..scroll_amount {
|
||||
table.scroll_down();
|
||||
}
|
||||
let after_down_id = table.current_selection().id;
|
||||
|
||||
// Position should increase (up to max)
|
||||
prop_assert!(after_down_id >= initial_id);
|
||||
prop_assert!(after_down_id < list_size as i64);
|
||||
|
||||
// Scroll back up
|
||||
for _ in 0..scroll_amount {
|
||||
table.scroll_up();
|
||||
}
|
||||
|
||||
// Should return to initial position (or 0 if we hit the top)
|
||||
prop_assert!(table.current_selection().id <= initial_id);
|
||||
}
|
||||
|
||||
/// Property test: Empty tables handle operations gracefully
|
||||
#[test]
|
||||
fn test_empty_table_safety(_scroll_ops in 0usize..50) {
|
||||
let table = StatefulTable::<Movie>::default();
|
||||
|
||||
// Empty table operations should be safe
|
||||
prop_assert!(table.is_empty());
|
||||
prop_assert!(table.items.is_empty());
|
||||
}
|
||||
|
||||
/// Property test: Navigation operations maintain consistency
|
||||
#[test]
|
||||
fn test_navigation_consistency(pushes in 1usize..20) {
|
||||
let mut app = App::test_default();
|
||||
let initial_route = app.get_current_route();
|
||||
|
||||
// Push multiple routes
|
||||
let routes = vec![
|
||||
ActiveRadarrBlock::Movies,
|
||||
ActiveRadarrBlock::Collections,
|
||||
ActiveRadarrBlock::Downloads,
|
||||
ActiveRadarrBlock::Blocklist,
|
||||
];
|
||||
|
||||
for i in 0..pushes {
|
||||
let route = routes[i % routes.len()];
|
||||
app.push_navigation_stack(route.into());
|
||||
}
|
||||
|
||||
// Current route should be the last pushed
|
||||
let last_pushed = routes[(pushes - 1) % routes.len()];
|
||||
prop_assert_eq!(app.get_current_route(), last_pushed.into());
|
||||
|
||||
// Pop all routes
|
||||
for _ in 0..pushes {
|
||||
app.pop_navigation_stack();
|
||||
}
|
||||
|
||||
// Should return to initial route
|
||||
prop_assert_eq!(app.get_current_route(), initial_route);
|
||||
}
|
||||
|
||||
/// Property test: String input handling is safe
|
||||
#[test]
|
||||
fn test_string_input_safety(input in text_input_string()) {
|
||||
// String operations should never panic
|
||||
let _lowercase = input.to_lowercase();
|
||||
let _uppercase = input.to_uppercase();
|
||||
let _trimmed = input.trim();
|
||||
let _len = input.len();
|
||||
let _chars: Vec<char> = input.chars().collect();
|
||||
|
||||
// All operations completed without panic
|
||||
prop_assert!(true);
|
||||
}
|
||||
|
||||
/// Property test: Table maintains data integrity after operations
|
||||
#[test]
|
||||
fn test_table_data_integrity(
|
||||
list_size in 1usize..100
|
||||
) {
|
||||
let mut table = StatefulTable::<Movie>::default();
|
||||
let movies: Vec<Movie> = (0..list_size).map(|i| {
|
||||
let mut movie = Movie::default();
|
||||
movie.id = i as i64;
|
||||
movie.title = format!("Movie {}", i).into();
|
||||
movie
|
||||
}).collect();
|
||||
|
||||
table.set_items(movies.clone());
|
||||
let original_count = table.items.len();
|
||||
|
||||
// Count should remain the same after various operations
|
||||
prop_assert_eq!(table.items.len(), original_count);
|
||||
|
||||
// All original items should still be present
|
||||
for movie in &movies {
|
||||
prop_assert!(table.items.iter().any(|m| m.id == movie.id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Property test: Page up/down maintains bounds
|
||||
#[test]
|
||||
fn test_page_navigation_bounds(
|
||||
list_size in list_size(),
|
||||
page_ops in 0usize..10
|
||||
) {
|
||||
let mut table = StatefulTable::<Movie>::default();
|
||||
let movies: Vec<Movie> = (0..list_size).map(|i| {
|
||||
let mut movie = Movie::default();
|
||||
movie.id = i as i64;
|
||||
movie
|
||||
}).collect();
|
||||
|
||||
table.set_items(movies);
|
||||
|
||||
// Perform page operations
|
||||
for i in 0..page_ops {
|
||||
if i % 2 == 0 {
|
||||
table.page_down();
|
||||
} else {
|
||||
table.page_up();
|
||||
}
|
||||
|
||||
// Should never exceed bounds
|
||||
let current = table.current_selection();
|
||||
prop_assert!(current.id >= 0);
|
||||
prop_assert!(current.id < list_size as i64);
|
||||
}
|
||||
}
|
||||
|
||||
/// Property test: Table filtering reduces or maintains size
|
||||
#[test]
|
||||
fn test_table_filter_size_invariant(
|
||||
list_size in list_size(),
|
||||
filter_term in text_input_string()
|
||||
) {
|
||||
let mut table = StatefulTable::<Movie>::default();
|
||||
let movies: Vec<Movie> = (0..list_size).map(|i| {
|
||||
let mut movie = Movie::default();
|
||||
movie.id = i as i64;
|
||||
movie.title = format!("Test Movie {}", i % 10).into();
|
||||
movie
|
||||
}).collect();
|
||||
|
||||
table.set_items(movies.clone());
|
||||
let original_size = table.items.len();
|
||||
|
||||
// Apply filter
|
||||
if !filter_term.is_empty() {
|
||||
let filtered: Vec<Movie> = movies.into_iter()
|
||||
.filter(|m| m.title.text.to_lowercase().contains(&filter_term.to_lowercase()))
|
||||
.collect();
|
||||
table.set_items(filtered);
|
||||
}
|
||||
|
||||
// Filtered size should be <= original
|
||||
prop_assert!(table.items.len() <= original_size);
|
||||
|
||||
// Selection should still be valid if table not empty
|
||||
if !table.items.is_empty() {
|
||||
let current = table.current_selection();
|
||||
prop_assert!(current.id >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,4 +452,40 @@ mod test_utils {
|
||||
assert!(app.should_refresh);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_modal_present {
|
||||
($modal:expr) => {
|
||||
assert!($modal.is_some(), "Expected modal to be present");
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_modal_absent {
|
||||
($modal:expr) => {
|
||||
assert!($modal.is_none(), "Expected modal to be absent");
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_navigation_pushed {
|
||||
($app:expr, $expected_route:expr) => {
|
||||
pretty_assertions::assert_eq!(
|
||||
$app.get_current_route(),
|
||||
$expected_route,
|
||||
"Expected route to be pushed onto navigation stack"
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_navigation_popped {
|
||||
($app:expr, $expected_route:expr) => {
|
||||
pretty_assertions::assert_eq!(
|
||||
$app.get_current_route(),
|
||||
$expected_route,
|
||||
"Expected route after popping navigation stack"
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::assert_modal_absent;
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::sonarr_models::Series;
|
||||
use crate::{assert_modal_present, assert_navigation_popped};
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::context_clues::SERVARR_CONTEXT_CLUES;
|
||||
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
|
||||
use crate::app::radarr::radarr_context_clues::{
|
||||
LIBRARY_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES,
|
||||
};
|
||||
use crate::event::Key;
|
||||
use crate::handlers::handle_events;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use crate::handlers::{handle_events, populate_keymapping_table};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use crate::models::servarr_models::KeybindingItem;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
|
||||
#[test]
|
||||
fn test_handle_clear_errors() {
|
||||
@@ -23,7 +33,7 @@ mod tests {
|
||||
|
||||
handle_clear_errors(&mut app);
|
||||
|
||||
assert!(app.error.text.is_empty());
|
||||
assert_is_empty!(app.error.text);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -46,7 +56,7 @@ mod tests {
|
||||
|
||||
handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app);
|
||||
|
||||
assert_eq!(app.get_current_route(), base_block);
|
||||
assert_navigation_popped!(app, base_block);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -63,7 +73,7 @@ mod tests {
|
||||
handle_events(DEFAULT_KEYBINDINGS.previous_servarr.key, &mut app);
|
||||
|
||||
assert_eq!(app.server_tabs.get_active_route(), left_block.into());
|
||||
assert_eq!(app.get_current_route(), left_block.into());
|
||||
assert_navigation_pushed!(app, left_block.into());
|
||||
assert!(app.is_first_render);
|
||||
assert_eq!(app.error, HorizontallyScrollableText::default());
|
||||
assert!(app.cancellation_token.is_cancelled());
|
||||
@@ -76,12 +86,88 @@ mod tests {
|
||||
handle_events(DEFAULT_KEYBINDINGS.next_servarr.key, &mut app);
|
||||
|
||||
assert_eq!(app.server_tabs.get_active_route(), right_block.into());
|
||||
assert_eq!(app.get_current_route(), right_block.into());
|
||||
assert_navigation_pushed!(app, right_block.into());
|
||||
assert!(app.is_first_render);
|
||||
assert_eq!(app.error, HorizontallyScrollableText::default());
|
||||
assert!(app.cancellation_token.is_cancelled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_populate_keybindings_table_on_help_button_press() {
|
||||
let mut app = App::test_default();
|
||||
let expected_keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
||||
.collect::<Vec<_>>();
|
||||
app.push_navigation_stack(ActiveKeybindingBlock::Help.into());
|
||||
|
||||
handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app);
|
||||
|
||||
assert_modal_present!(app.keymapping_table);
|
||||
assert_eq!(
|
||||
expected_keybinding_items,
|
||||
app.keymapping_table.unwrap().items
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_ignore_help_button_when_ignore_special_keys_for_textbox_input_is_true() {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||
|
||||
handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app);
|
||||
|
||||
assert_modal_absent!(app.keymapping_table);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_empties_keybindings_table_on_help_button_press_when_keybindings_table_is_already_populated()
|
||||
{
|
||||
let mut app = App::test_default();
|
||||
let keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
||||
.collect::<Vec<_>>();
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(keybinding_items);
|
||||
app.keymapping_table = Some(stateful_table);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||
|
||||
handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app);
|
||||
|
||||
assert_modal_absent!(app.keymapping_table);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_shows_keymapping_popup_when_keymapping_table_is_populated() {
|
||||
let mut app = App::test_default();
|
||||
let keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
||||
.collect::<Vec<_>>();
|
||||
let mut stateful_table = StatefulTable::default();
|
||||
stateful_table.set_items(keybinding_items);
|
||||
app.keymapping_table = Some(stateful_table);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||
let expected_selection = KeybindingItem {
|
||||
key: SERVARR_CONTEXT_CLUES[1].0.key.to_string(),
|
||||
alt_key: SERVARR_CONTEXT_CLUES[1]
|
||||
.0
|
||||
.alt
|
||||
.map_or(String::new(), |k| k.to_string()),
|
||||
desc: SERVARR_CONTEXT_CLUES[1].1.to_string(),
|
||||
};
|
||||
|
||||
handle_events(DEFAULT_KEYBINDINGS.down.key, &mut app);
|
||||
|
||||
assert_modal_present!(app.keymapping_table);
|
||||
assert_eq!(
|
||||
&expected_selection,
|
||||
app.keymapping_table.unwrap().current_selection()
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_handle_prompt_toggle_left_right_radarr(#[values(Key::Left, Key::Right)] key: Key) {
|
||||
let mut app = App::test_default();
|
||||
@@ -113,4 +199,95 @@ mod tests {
|
||||
|
||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_populate_keymapping_table_global_options() {
|
||||
let expected_keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||
.iter()
|
||||
.map(|(key, desc)| {
|
||||
let (key, alt_key) = if key.alt.is_some() {
|
||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
||||
} else {
|
||||
(key.key.to_string(), String::new())
|
||||
};
|
||||
KeybindingItem {
|
||||
key,
|
||||
alt_key,
|
||||
desc: desc.to_string(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveKeybindingBlock::Help.into());
|
||||
|
||||
populate_keymapping_table(&mut app);
|
||||
|
||||
assert_modal_present!(app.keymapping_table);
|
||||
assert_eq!(
|
||||
expected_keybinding_items,
|
||||
app.keymapping_table.unwrap().items
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_populate_keymapping_table_populates_servarr_specific_tab_info_before_global_options() {
|
||||
let mut expected_keybinding_items = LIBRARY_CONTEXT_CLUES
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
||||
.collect::<Vec<_>>();
|
||||
expected_keybinding_items.extend(
|
||||
SERVARR_CONTEXT_CLUES
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)),
|
||||
);
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||
|
||||
populate_keymapping_table(&mut app);
|
||||
|
||||
assert_modal_present!(app.keymapping_table);
|
||||
assert_eq!(
|
||||
expected_keybinding_items,
|
||||
app.keymapping_table.unwrap().items
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_populate_keymapping_table_populates_delegated_servarr_context_provider_options_before_global_options()
|
||||
{
|
||||
let mut expected_keybinding_items = MOVIE_DETAILS_CONTEXT_CLUES
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
||||
.collect::<Vec<_>>();
|
||||
expected_keybinding_items.extend(
|
||||
SERVARR_CONTEXT_CLUES
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)),
|
||||
);
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = RadarrData::default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into());
|
||||
|
||||
populate_keymapping_table(&mut app);
|
||||
|
||||
assert_modal_present!(app.keymapping_table);
|
||||
assert_eq!(
|
||||
expected_keybinding_items,
|
||||
app.keymapping_table.unwrap().items
|
||||
);
|
||||
}
|
||||
|
||||
fn context_clue_to_keybinding_item(key: &KeyBinding, desc: &&str) -> KeybindingItem {
|
||||
let (key, alt_key) = if key.alt.is_some() {
|
||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
||||
} else {
|
||||
(key.key.to_string(), String::new())
|
||||
};
|
||||
KeybindingItem {
|
||||
key,
|
||||
alt_key,
|
||||
desc: desc.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "keybinding_handler_tests.rs"]
|
||||
mod keybinding_handler_tests;
|
||||
|
||||
pub(super) struct KeybindingHandler<'a, 'b> {
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
let keybinding_table_handling_config = TableHandlingConfig::new(self.app.get_current_route());
|
||||
|
||||
if !handle_table(
|
||||
self,
|
||||
|app| app.keymapping_table.as_mut().unwrap(),
|
||||
keybinding_table_handling_config,
|
||||
) {
|
||||
self.handle_key_event();
|
||||
}
|
||||
}
|
||||
|
||||
fn accepts(_active_block: ActiveKeybindingBlock) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
_active_block: ActiveKeybindingBlock,
|
||||
_context: Option<ActiveKeybindingBlock>,
|
||||
) -> KeybindingHandler<'a, 'b> {
|
||||
KeybindingHandler { key, app }
|
||||
}
|
||||
|
||||
fn get_key(&self) -> Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.app.keymapping_table.is_some()
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {}
|
||||
|
||||
fn handle_scroll_down(&mut self) {}
|
||||
|
||||
fn handle_home(&mut self) {}
|
||||
|
||||
fn handle_end(&mut self) {}
|
||||
|
||||
fn handle_delete(&mut self) {}
|
||||
|
||||
fn handle_left_right_action(&mut self) {}
|
||||
|
||||
fn handle_submit(&mut self) {}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
self.app.keymapping_table = None;
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::KeybindingHandler;
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use rstest::rstest;
|
||||
|
||||
mod test_handle_esc {
|
||||
use super::*;
|
||||
use crate::assert_modal_absent;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||
|
||||
#[test]
|
||||
fn test_esc_empties_keymapping_table() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
|
||||
KeybindingHandler::new(ESC_KEY, &mut app, ActiveKeybindingBlock::Help, None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
|
||||
assert_modal_absent!(app.keymapping_table);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keybinding_handler_accepts() {
|
||||
assert!(KeybindingHandler::accepts(ActiveKeybindingBlock::Help));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keybinding_handler_not_ready_when_keybinding_is_empty() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = false;
|
||||
|
||||
let handler = KeybindingHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveKeybindingBlock::Help,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(!handler.is_ready());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_keybinding_handler_ready_when_keymapping_table_is_not_empty(
|
||||
#[values(true, false)] is_loading: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.keymapping_table = Some(StatefulTable::default());
|
||||
app.is_loading = is_loading;
|
||||
|
||||
let handler = KeybindingHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveKeybindingBlock::Help,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(handler.is_ready());
|
||||
}
|
||||
}
|
||||
+86
-23
@@ -1,11 +1,20 @@
|
||||
use radarr_handlers::RadarrHandler;
|
||||
use sonarr_handlers::SonarrHandler;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::context_clues::{
|
||||
ContextClueProvider, SERVARR_CONTEXT_CLUES, ServarrContextClueProvider,
|
||||
};
|
||||
use crate::app::key_binding::KeyBinding;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::keybinding_handler::KeybindingHandler;
|
||||
use crate::matches_key;
|
||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||
use crate::models::servarr_models::KeybindingItem;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::{HorizontallyScrollableText, Route};
|
||||
|
||||
mod keybinding_handler;
|
||||
mod radarr_handlers;
|
||||
mod sonarr_handlers;
|
||||
|
||||
@@ -22,40 +31,42 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
||||
fn handle_key_event(&mut self) {
|
||||
let key = self.get_key();
|
||||
match key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.up.key => {
|
||||
_ if matches_key!(up, key, self.ignore_special_keys()) => {
|
||||
if self.is_ready() {
|
||||
self.handle_scroll_up();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.down.key => {
|
||||
_ if matches_key!(down, key, self.ignore_special_keys()) => {
|
||||
if self.is_ready() {
|
||||
self.handle_scroll_down();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.home.key => {
|
||||
_ if matches_key!(home, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_home();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.end.key => {
|
||||
_ if matches_key!(end, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_end();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.delete.key => {
|
||||
_ if matches_key!(delete, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_delete();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
_ if matches_key!(left, key, self.ignore_special_keys())
|
||||
|| matches_key!(right, key, self.ignore_special_keys()) =>
|
||||
{
|
||||
self.handle_left_right_action()
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.submit.key => {
|
||||
_ if matches_key!(submit, key) => {
|
||||
if self.is_ready() {
|
||||
self.handle_submit();
|
||||
}
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(),
|
||||
_ if matches_key!(esc, key) => self.handle_esc(),
|
||||
_ => {
|
||||
if self.is_ready() {
|
||||
self.handle_char_key_event();
|
||||
@@ -71,6 +82,7 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
||||
fn accepts(active_block: T) -> bool;
|
||||
fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
|
||||
fn get_key(&self) -> Key;
|
||||
fn ignore_special_keys(&self) -> bool;
|
||||
fn is_ready(&self) -> bool;
|
||||
fn handle_scroll_up(&mut self);
|
||||
fn handle_scroll_down(&mut self);
|
||||
@@ -81,21 +93,32 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
|
||||
fn handle_submit(&mut self);
|
||||
fn handle_esc(&mut self);
|
||||
fn handle_char_key_event(&mut self);
|
||||
fn app_mut(&mut self) -> &mut App<'b>;
|
||||
fn current_route(&self) -> Route;
|
||||
}
|
||||
|
||||
pub fn handle_events(key: Key, app: &mut App<'_>) {
|
||||
if key == DEFAULT_KEYBINDINGS.next_servarr.key {
|
||||
if matches_key!(next_servarr, key) {
|
||||
app.reset();
|
||||
app.server_tabs.next();
|
||||
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
|
||||
app.cancellation_token.cancel();
|
||||
} else if key == DEFAULT_KEYBINDINGS.previous_servarr.key {
|
||||
} else if matches_key!(previous_servarr, key) {
|
||||
app.reset();
|
||||
app.server_tabs.previous();
|
||||
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
|
||||
app.cancellation_token.cancel();
|
||||
} else if matches_key!(help, key) && !app.ignore_special_keys_for_textbox_input {
|
||||
if app.keymapping_table.is_none() {
|
||||
populate_keymapping_table(app);
|
||||
} else {
|
||||
app.keymapping_table = None;
|
||||
}
|
||||
} else {
|
||||
match app.get_current_route() {
|
||||
_ if app.keymapping_table.is_some() => {
|
||||
KeybindingHandler::new(key, app, ActiveKeybindingBlock::Help, None).handle();
|
||||
}
|
||||
Route::Radarr(active_radarr_block, context) => {
|
||||
RadarrHandler::new(key, app, active_radarr_block, context).handle()
|
||||
}
|
||||
@@ -107,6 +130,48 @@ pub fn handle_events(key: Key, app: &mut App<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_keymapping_table(app: &mut App<'_>) {
|
||||
let context_clue_to_keybinding_item = |key: &KeyBinding, desc: &&str| {
|
||||
let (key, alt_key) = if key.alt.is_some() {
|
||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
||||
} else {
|
||||
(key.key.to_string(), String::new())
|
||||
};
|
||||
KeybindingItem {
|
||||
key,
|
||||
alt_key,
|
||||
desc: desc.to_string(),
|
||||
}
|
||||
};
|
||||
let mut keybindings = Vec::new();
|
||||
let global_keybindings = Vec::from(SERVARR_CONTEXT_CLUES)
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(contextual_help) = app.server_tabs.get_active_route_contextual_help() {
|
||||
keybindings.extend(
|
||||
contextual_help
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(contextual_help) = ServarrContextClueProvider::get_context_clues(app) {
|
||||
keybindings.extend(
|
||||
contextual_help
|
||||
.iter()
|
||||
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)),
|
||||
);
|
||||
}
|
||||
|
||||
keybindings.extend(global_keybindings);
|
||||
|
||||
let mut table = StatefulTable::default();
|
||||
table.set_items(keybindings);
|
||||
app.keymapping_table = Some(table);
|
||||
}
|
||||
|
||||
fn handle_clear_errors(app: &mut App<'_>) {
|
||||
if !app.error.text.is_empty() {
|
||||
app.error = HorizontallyScrollableText::default();
|
||||
@@ -115,17 +180,15 @@ fn handle_clear_errors(app: &mut App<'_>) {
|
||||
|
||||
fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
|
||||
match key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => {
|
||||
match app.get_current_route() {
|
||||
Route::Radarr(_, _) => {
|
||||
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
|
||||
}
|
||||
Route::Sonarr(_, _) => {
|
||||
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
|
||||
}
|
||||
_ => (),
|
||||
_ if matches_key!(left, key) || matches_key!(right, key) => match app.get_current_route() {
|
||||
Route::Radarr(_, _) => {
|
||||
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
|
||||
}
|
||||
}
|
||||
Route::Sonarr(_, _) => {
|
||||
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -149,7 +212,7 @@ macro_rules! handle_text_box_left_right_keys {
|
||||
macro_rules! handle_text_box_keys {
|
||||
($self:expr, $key:expr, $input:expr) => {
|
||||
match $self.key {
|
||||
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.backspace.key => {
|
||||
_ if $crate::matches_key!(backspace, $key) => {
|
||||
$input.pop();
|
||||
}
|
||||
Key::Char(character) => {
|
||||
@@ -165,7 +228,7 @@ macro_rules! handle_prompt_left_right_keys {
|
||||
($self:expr, $confirm_prompt:expr, $data:ident) => {
|
||||
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
|
||||
handle_prompt_toggle($self.app, $self.key);
|
||||
} else if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key {
|
||||
} else if $crate::matches_key!(left, $self.key) {
|
||||
$self.app.data.$data.selected_block.left();
|
||||
} else {
|
||||
$self.app.data.$data.selected_block.right();
|
||||
|
||||
@@ -4,13 +4,15 @@ mod tests {
|
||||
|
||||
use chrono::DateTime;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::blocklist::{blocklist_sorting_options, BlocklistHandler};
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::radarr_handlers::blocklist::{BlocklistHandler, blocklist_sorting_options};
|
||||
use crate::models::radarr_models::{BlocklistItem, BlocklistItemMovie};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::servarr_models::{Language, Quality, QualityWrapper};
|
||||
@@ -29,10 +31,7 @@ mod tests {
|
||||
|
||||
BlocklistHandler::new(DELETE_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::DeleteBlocklistItemPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -53,6 +52,7 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_navigation_pushed;
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_tab_left(#[values(true, false)] is_ready: bool) {
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Downloads.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -93,10 +93,7 @@ mod tests {
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
ActiveRadarrBlock::RootFolders.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::RootFolders.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::RootFolders.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -121,11 +118,11 @@ mod tests {
|
||||
}
|
||||
|
||||
mod test_handle_submit {
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
|
||||
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||
@@ -138,10 +135,7 @@ mod tests {
|
||||
|
||||
BlocklistHandler::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::BlocklistItemDetails.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::BlocklistItemDetails.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -185,7 +179,7 @@ mod tests {
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), base_route.into());
|
||||
assert_navigation_popped!(app, base_route.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -204,18 +198,18 @@ mod tests {
|
||||
BlocklistHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Blocklist.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_navigation_popped;
|
||||
|
||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||
|
||||
@@ -239,7 +233,7 @@ mod tests {
|
||||
|
||||
BlocklistHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), base_block.into());
|
||||
assert_navigation_popped!(app, base_block.into());
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
@@ -257,7 +251,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Blocklist.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -270,7 +264,7 @@ mod tests {
|
||||
|
||||
DownloadsHandler::new(ESC_KEY, &mut app, ActiveRadarrBlock::Blocklist, None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Blocklist.into());
|
||||
assert!(app.error.text.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -282,6 +276,7 @@ mod tests {
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
use crate::{assert_navigation_popped, assert_navigation_pushed};
|
||||
|
||||
#[test]
|
||||
fn test_refresh_blocklist_key() {
|
||||
@@ -297,7 +292,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Blocklist.into());
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
|
||||
@@ -333,10 +328,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -391,7 +383,7 @@ mod tests {
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), base_route.into());
|
||||
assert_navigation_popped!(app, base_route.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,6 +533,25 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_blocklist_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = BlocklistHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_blocklist_item_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::radarr_models::BlocklistItem;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
@@ -22,13 +21,6 @@ pub(super) struct BlocklistHandler<'a, 'b> {
|
||||
}
|
||||
|
||||
impl BlocklistHandler<'_, '_> {
|
||||
handle_table_events!(
|
||||
self,
|
||||
blocklist,
|
||||
self.app.data.radarr_data.blocklist,
|
||||
BlocklistItem
|
||||
);
|
||||
|
||||
fn extract_blocklist_item_id(&self) -> i64 {
|
||||
self.app.data.radarr_data.blocklist.current_selection().id
|
||||
}
|
||||
@@ -39,10 +31,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
let blocklist_table_handling_config =
|
||||
TableHandlingConfig::new(ActiveRadarrBlock::Blocklist.into())
|
||||
.sorting_block(ActiveRadarrBlock::BlocklistSortPrompt.into())
|
||||
.sort_by_fn(|a: &BlocklistItem, b: &BlocklistItem| a.id.cmp(&b.id))
|
||||
.sort_options(blocklist_sorting_options());
|
||||
|
||||
if !self.handle_blocklist_table_events(blocklist_table_handling_config) {
|
||||
if !handle_table(
|
||||
self,
|
||||
|app| &mut app.data.radarr_data.blocklist,
|
||||
blocklist_table_handling_config,
|
||||
) {
|
||||
self.handle_key_event();
|
||||
}
|
||||
}
|
||||
@@ -51,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
BLOCKLIST_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -143,10 +142,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Blocklist => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.clear.key => {
|
||||
_ if matches_key!(clear, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||
@@ -154,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
|
||||
self.extract_blocklist_item_id(),
|
||||
@@ -164,7 +163,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
|
||||
|
||||
@@ -174,6 +173,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::radarr_models::CollectionMovie;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, COLLECTION_DETAILS_BLOCKS,
|
||||
ADD_MOVIE_SELECTION_BLOCKS, ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS,
|
||||
EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::BlockSelectionState;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "collection_details_handler_tests.rs"]
|
||||
@@ -23,21 +21,18 @@ pub(super) struct CollectionDetailsHandler<'a, 'b> {
|
||||
_context: Option<ActiveRadarrBlock>,
|
||||
}
|
||||
|
||||
impl CollectionDetailsHandler<'_, '_> {
|
||||
handle_table_events!(
|
||||
self,
|
||||
collection_movies,
|
||||
self.app.data.radarr_data.collection_movies,
|
||||
CollectionMovie
|
||||
);
|
||||
}
|
||||
impl CollectionDetailsHandler<'_, '_> {}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
let collection_movies_table_handling_config =
|
||||
TableHandlingConfig::new(ActiveRadarrBlock::CollectionDetails.into());
|
||||
|
||||
if !self.handle_collection_movies_table_events(collection_movies_table_handling_config) {
|
||||
if !handle_table(
|
||||
self,
|
||||
|app| &mut app.data.radarr_data.collection_movies,
|
||||
collection_movies_table_handling_config,
|
||||
) {
|
||||
self.handle_key_event();
|
||||
}
|
||||
}
|
||||
@@ -46,6 +41,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
|
||||
COLLECTION_DETAILS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -130,7 +129,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails
|
||||
&& self.key == DEFAULT_KEYBINDINGS.edit.key
|
||||
&& matches_key!(edit, self.key)
|
||||
{
|
||||
self.app.push_navigation_stack(
|
||||
(
|
||||
@@ -144,4 +143,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
|
||||
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_modal_absent;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
|
||||
use crate::models::radarr_models::CollectionMovie;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS,
|
||||
};
|
||||
|
||||
mod test_handle_submit {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ADD_MOVIE_SELECTION_BLOCKS;
|
||||
use crate::models::BlockSelectionState;
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -50,45 +52,51 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
assert_navigation_pushed!(
|
||||
app,
|
||||
(
|
||||
ActiveRadarrBlock::AddMoviePrompt,
|
||||
Some(ActiveRadarrBlock::CollectionDetails)
|
||||
)
|
||||
.into()
|
||||
);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.add_movie_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.monitor_list
|
||||
.items
|
||||
.is_empty());
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.add_movie_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.monitor_list
|
||||
.items
|
||||
.is_empty()
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.selected_block.get_active_block(),
|
||||
ActiveRadarrBlock::AddMovieSelectRootFolder
|
||||
);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.add_movie_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.minimum_availability_list
|
||||
.items
|
||||
.is_empty());
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.add_movie_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.quality_profile_list
|
||||
.items
|
||||
.is_empty());
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.add_movie_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.minimum_availability_list
|
||||
.items
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.add_movie_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.quality_profile_list
|
||||
.items
|
||||
.is_empty()
|
||||
);
|
||||
assert_str_eq!(
|
||||
app
|
||||
.data
|
||||
@@ -125,7 +133,7 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::CollectionDetails.into()
|
||||
);
|
||||
assert!(app.data.radarr_data.add_movie_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.add_movie_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -150,16 +158,13 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::ViewMovieOverview.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::ViewMovieOverview.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use crate::assert_navigation_popped;
|
||||
use rstest::rstest;
|
||||
|
||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||
@@ -184,11 +189,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert!(app.data.radarr_data.collection_movies.items.is_empty());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert_is_empty!(app.data.radarr_data.collection_movies.items);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -205,10 +207,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::CollectionDetails.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::CollectionDetails.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +219,7 @@ mod tests {
|
||||
use crate::models::radarr_models::{Collection, MinimumAvailability};
|
||||
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
RadarrData, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
EDIT_COLLECTION_SELECTION_BLOCKS, RadarrData,
|
||||
};
|
||||
use crate::test_edit_collection_key;
|
||||
|
||||
@@ -263,7 +262,7 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::CollectionDetails.into()
|
||||
);
|
||||
assert!(app.data.radarr_data.edit_collection_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_collection_modal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +277,25 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_collection_details_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = CollectionDetailsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collection_details_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -7,16 +7,18 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_modal_absent;
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::collections::{
|
||||
collections_sorting_options, CollectionsHandler,
|
||||
};
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::radarr_handlers::collections::{
|
||||
CollectionsHandler, collections_sorting_options,
|
||||
};
|
||||
use crate::models::radarr_models::{Collection, CollectionMovie};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, COLLECTIONS_BLOCKS, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS,
|
||||
ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, COLLECTIONS_BLOCKS, EDIT_COLLECTION_BLOCKS,
|
||||
};
|
||||
use crate::test_handler_delegation;
|
||||
|
||||
@@ -25,6 +27,7 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_navigation_pushed;
|
||||
|
||||
#[rstest]
|
||||
fn test_collections_tab_left(#[values(true, false)] is_ready: bool) {
|
||||
@@ -44,7 +47,7 @@ mod tests {
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
ActiveRadarrBlock::Movies.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Movies.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -65,7 +68,7 @@ mod tests {
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
ActiveRadarrBlock::Downloads.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Downloads.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -97,9 +100,9 @@ mod tests {
|
||||
}
|
||||
|
||||
mod test_handle_submit {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -116,10 +119,7 @@ mod tests {
|
||||
|
||||
CollectionsHandler::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::Collections, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::CollectionDetails.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::CollectionDetails.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -166,10 +166,7 @@ mod tests {
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::UpdateCollections)
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -192,17 +189,13 @@ mod tests {
|
||||
.handle();
|
||||
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
|
||||
|
||||
use super::*;
|
||||
@@ -224,10 +217,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
@@ -242,11 +232,8 @@ mod tests {
|
||||
|
||||
CollectionsHandler::new(ESC_KEY, &mut app, ActiveRadarrBlock::Collections, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert!(app.error.text.is_empty());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert_is_empty!(app.error.text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,10 +245,10 @@ mod tests {
|
||||
use crate::models::radarr_models::MinimumAvailability;
|
||||
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
RadarrData, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
EDIT_COLLECTION_SELECTION_BLOCKS, RadarrData,
|
||||
};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::test_edit_collection_key;
|
||||
use crate::{assert_navigation_popped, test_edit_collection_key};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -298,7 +285,7 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert!(app.data.radarr_data.edit_collection_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_collection_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -318,10 +305,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -367,10 +351,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
|
||||
@@ -424,10 +405,7 @@ mod tests {
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::UpdateCollections)
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,6 +567,25 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_collections_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = CollectionsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collections_handler_not_ready_when_loading() {
|
||||
let mut app = App::test_default();
|
||||
@@ -644,7 +641,7 @@ mod tests {
|
||||
Collection {
|
||||
id: 3,
|
||||
title: "test 1".into(),
|
||||
movies: Some(iter::repeat(CollectionMovie::default()).take(3).collect()),
|
||||
movies: Some(iter::repeat_n(CollectionMovie::default(), 3).collect()),
|
||||
root_folder_path: Some("/nfs/movies".into()),
|
||||
quality_profile_id: 1,
|
||||
search_on_add: false,
|
||||
@@ -654,7 +651,7 @@ mod tests {
|
||||
Collection {
|
||||
id: 2,
|
||||
title: "test 2".into(),
|
||||
movies: Some(iter::repeat(CollectionMovie::default()).take(7).collect()),
|
||||
movies: Some(iter::repeat_n(CollectionMovie::default(), 7).collect()),
|
||||
root_folder_path: Some("/htpc/movies".into()),
|
||||
quality_profile_id: 3,
|
||||
search_on_add: true,
|
||||
@@ -664,7 +661,7 @@ mod tests {
|
||||
Collection {
|
||||
id: 1,
|
||||
title: "test 3".into(),
|
||||
movies: Some(iter::repeat(CollectionMovie::default()).take(1).collect()),
|
||||
movies: Some(iter::repeat_n(CollectionMovie::default(), 1).collect()),
|
||||
root_folder_path: Some("/nfs/some/stupidly/long/path/to/test/with".into()),
|
||||
quality_profile_id: 1,
|
||||
search_on_add: false,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::models::Scrollable;
|
||||
use crate::models::radarr_models::EditCollectionParams;
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
|
||||
use crate::models::Scrollable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_collection_handler_tests.rs"]
|
||||
@@ -69,6 +68,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
EDIT_COLLECTION_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -262,7 +265,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
self.app.should_ignore_quit_key = true;
|
||||
self.app.ignore_special_keys_for_textbox_input = true;
|
||||
}
|
||||
ActiveRadarrBlock::EditCollectionToggleMonitored => {
|
||||
self
|
||||
@@ -311,7 +314,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
| ActiveRadarrBlock::EditCollectionSelectQualityProfile => self.app.pop_navigation_stack(),
|
||||
ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
self.app.ignore_special_keys_for_textbox_input = false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -321,7 +324,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
self.app.ignore_special_keys_for_textbox_input = false;
|
||||
}
|
||||
ActiveRadarrBlock::EditCollectionPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
@@ -354,7 +357,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
ActiveRadarrBlock::EditCollectionPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::EditCollectionConfirmPrompt
|
||||
&& key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
|
||||
@@ -368,4 +371,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::assert_str_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_modal_absent;
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
|
||||
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::collection;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::radarr_models::{Collection, EditCollectionParams, MinimumAvailability};
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
@@ -21,9 +24,9 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::EDIT_COLLECTION_SELECTION_BLOCKS;
|
||||
use crate::models::BlockSelectionState;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -442,13 +445,13 @@ mod tests {
|
||||
}
|
||||
|
||||
mod test_handle_submit {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::models::servarr_data::radarr::modals::EditCollectionModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::EDIT_COLLECTION_SELECTION_BLOCKS;
|
||||
use crate::models::{BlockSelectionState, Route};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -457,7 +460,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_edit_collection_root_folder_path_input_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal {
|
||||
path: "Test Path".into(),
|
||||
..EditCollectionModal::default()
|
||||
@@ -473,20 +476,19 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_collection_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.path
|
||||
.text
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditCollectionPrompt.into()
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_collection_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.path
|
||||
.text
|
||||
.is_empty()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -511,11 +513,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -568,16 +567,13 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::EditCollection(expected_edit_collection_params))
|
||||
);
|
||||
assert!(app.should_refresh);
|
||||
assert!(app.data.radarr_data.edit_collection_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_collection_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -608,7 +604,7 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditCollectionPrompt.into()
|
||||
);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
assert!(!app.should_refresh);
|
||||
}
|
||||
|
||||
@@ -633,7 +629,7 @@ mod tests {
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), current_route);
|
||||
assert_eq!(
|
||||
assert_some_eq_x!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -641,7 +637,7 @@ mod tests {
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.monitored,
|
||||
Some(true)
|
||||
true
|
||||
);
|
||||
|
||||
EditCollectionHandler::new(
|
||||
@@ -653,7 +649,7 @@ mod tests {
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), current_route);
|
||||
assert_eq!(
|
||||
assert_some_eq_x!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -661,7 +657,7 @@ mod tests {
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.monitored,
|
||||
Some(false)
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -691,7 +687,7 @@ mod tests {
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), current_route);
|
||||
assert_eq!(
|
||||
assert_some_eq_x!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -699,7 +695,7 @@ mod tests {
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.search_on_add,
|
||||
Some(true)
|
||||
true
|
||||
);
|
||||
|
||||
EditCollectionHandler::new(
|
||||
@@ -711,7 +707,7 @@ mod tests {
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), current_route);
|
||||
assert_eq!(
|
||||
assert_some_eq_x!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
@@ -719,7 +715,7 @@ mod tests {
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.search_on_add,
|
||||
Some(false)
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -752,14 +748,14 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
assert_navigation_pushed!(
|
||||
app,
|
||||
(selected_block, Some(ActiveRadarrBlock::Collections)).into()
|
||||
);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
|
||||
if selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
|
||||
assert!(app.should_ignore_quit_key);
|
||||
assert!(app.ignore_special_keys_for_textbox_input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,22 +781,18 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditCollectionPrompt.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
|
||||
if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -811,7 +803,7 @@ mod tests {
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data = create_test_radarr_data();
|
||||
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into());
|
||||
|
||||
@@ -823,11 +815,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditCollectionPrompt.into()
|
||||
);
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -845,13 +834,10 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
let radarr_data = &app.data.radarr_data;
|
||||
|
||||
assert!(radarr_data.edit_collection_modal.is_none());
|
||||
assert_modal_absent!(radarr_data.edit_collection_modal);
|
||||
assert!(!radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
@@ -872,21 +858,19 @@ mod tests {
|
||||
|
||||
EditCollectionHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::{
|
||||
assert_navigation_popped,
|
||||
models::{
|
||||
BlockSelectionState,
|
||||
servarr_data::radarr::{
|
||||
modals::EditCollectionModal, radarr_data::EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
},
|
||||
BlockSelectionState,
|
||||
},
|
||||
network::radarr_network::RadarrEvent,
|
||||
};
|
||||
@@ -926,7 +910,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
|
||||
|
||||
EditCollectionHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
None,
|
||||
@@ -942,7 +926,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.path
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -995,16 +979,13 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Collections.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::EditCollection(expected_edit_collection_params))
|
||||
);
|
||||
assert!(app.should_refresh);
|
||||
assert!(app.data.radarr_data.edit_collection_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_collection_modal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,6 +1000,25 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_collection_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = EditCollectionHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_collection_params() {
|
||||
let mut app = App::test_default();
|
||||
@@ -1060,7 +1060,7 @@ mod tests {
|
||||
.build_edit_collection_params();
|
||||
|
||||
assert_eq!(edit_collection_params, expected_edit_collection_params);
|
||||
assert!(app.data.radarr_data.edit_collection_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_collection_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
|
||||
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::radarr_models::Collection;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::stateful_table::SortOption;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
mod collection_details_handler;
|
||||
@@ -29,21 +28,13 @@ pub(super) struct CollectionsHandler<'a, 'b> {
|
||||
context: Option<ActiveRadarrBlock>,
|
||||
}
|
||||
|
||||
impl CollectionsHandler<'_, '_> {
|
||||
handle_table_events!(
|
||||
self,
|
||||
collections,
|
||||
self.app.data.radarr_data.collections,
|
||||
Collection
|
||||
);
|
||||
}
|
||||
impl CollectionsHandler<'_, '_> {}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
let collections_table_handling_config =
|
||||
TableHandlingConfig::new(ActiveRadarrBlock::Collections.into())
|
||||
.sorting_block(ActiveRadarrBlock::CollectionsSortPrompt.into())
|
||||
.sort_by_fn(|a: &Collection, b: &Collection| a.id.cmp(&b.id))
|
||||
.sort_options(collections_sorting_options())
|
||||
.searching_block(ActiveRadarrBlock::SearchCollection.into())
|
||||
.search_error_block(ActiveRadarrBlock::SearchCollectionError.into())
|
||||
@@ -52,7 +43,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
.filter_error_block(ActiveRadarrBlock::FilterCollectionsError.into())
|
||||
.filter_field_fn(|collection| &collection.title.text);
|
||||
|
||||
if !self.handle_collections_table_events(collections_table_handling_config) {
|
||||
if !handle_table(
|
||||
self,
|
||||
|app| &mut app.data.radarr_data.collections,
|
||||
collections_table_handling_config,
|
||||
) {
|
||||
match self.active_radarr_block {
|
||||
_ if CollectionDetailsHandler::accepts(self.active_radarr_block) => {
|
||||
CollectionDetailsHandler::new(self.key, self.app, self.active_radarr_block, self.context)
|
||||
@@ -73,6 +68,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
|| COLLECTIONS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -145,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Collections => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.edit.key => {
|
||||
_ if matches_key!(edit, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
|
||||
@@ -154,18 +153,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
self.app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
|
||||
|
||||
@@ -175,6 +174,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
fn collections_sorting_options() -> Vec<SortOption<Collection>> {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
|
||||
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::download_record;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::radarr_models::DownloadRecord;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
|
||||
|
||||
@@ -30,10 +32,7 @@ mod tests {
|
||||
|
||||
DownloadsHandler::new(DELETE_KEY, &mut app, ActiveRadarrBlock::Downloads, None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::DeleteDownloadPrompt.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::DeleteDownloadPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -77,10 +76,7 @@ mod tests {
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::Collections.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Collections.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -101,7 +97,7 @@ mod tests {
|
||||
app.data.radarr_data.main_tabs.get_active_route(),
|
||||
ActiveRadarrBlock::Blocklist.into()
|
||||
);
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Blocklist.into());
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Blocklist.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -132,6 +128,7 @@ mod tests {
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_navigation_popped;
|
||||
|
||||
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||
|
||||
@@ -168,7 +165,7 @@ mod tests {
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), base_route.into());
|
||||
assert_navigation_popped!(app, base_route.into());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -190,16 +187,16 @@ mod tests {
|
||||
DownloadsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_eq!(app.get_current_route(), base_route.into());
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
assert_navigation_popped!(app, base_route.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::assert_navigation_popped;
|
||||
|
||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||
|
||||
@@ -217,7 +214,7 @@ mod tests {
|
||||
|
||||
DownloadsHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), base_block.into());
|
||||
assert_navigation_popped!(app, base_block.into());
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
}
|
||||
|
||||
@@ -231,17 +228,17 @@ mod tests {
|
||||
|
||||
DownloadsHandler::new(ESC_KEY, &mut app, ActiveRadarrBlock::Downloads, None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
|
||||
assert!(app.error.text.is_empty());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Downloads.into());
|
||||
assert_is_empty!(app.error.text);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -261,10 +258,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::UpdateDownloadsPrompt.into()
|
||||
);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::UpdateDownloadsPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -307,7 +301,7 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Downloads.into());
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::Downloads.into());
|
||||
assert!(app.should_refresh);
|
||||
}
|
||||
|
||||
@@ -372,7 +366,7 @@ mod tests {
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(expected_action)
|
||||
);
|
||||
assert_eq!(app.get_current_route(), base_route.into());
|
||||
assert_navigation_popped!(app, base_route.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +381,25 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_downloads_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = DownloadsHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_download_id() {
|
||||
let mut app = App::test_default();
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handle_table_events;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::table_handler::TableHandlingConfig;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::radarr_models::DownloadRecord;
|
||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
|
||||
use crate::matches_key;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
@@ -21,13 +19,6 @@ pub(super) struct DownloadsHandler<'a, 'b> {
|
||||
}
|
||||
|
||||
impl DownloadsHandler<'_, '_> {
|
||||
handle_table_events!(
|
||||
self,
|
||||
downloads,
|
||||
self.app.data.radarr_data.downloads,
|
||||
DownloadRecord
|
||||
);
|
||||
|
||||
fn extract_download_id(&self) -> i64 {
|
||||
self.app.data.radarr_data.downloads.current_selection().id
|
||||
}
|
||||
@@ -38,7 +29,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
let downloads_table_handling_config =
|
||||
TableHandlingConfig::new(ActiveRadarrBlock::Downloads.into());
|
||||
|
||||
if !self.handle_downloads_table_events(downloads_table_handling_config) {
|
||||
if !handle_table(
|
||||
self,
|
||||
|app| &mut app.data.radarr_data.downloads,
|
||||
downloads_table_handling_config,
|
||||
) {
|
||||
self.handle_key_event();
|
||||
}
|
||||
}
|
||||
@@ -47,6 +42,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
DOWNLOADS_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -130,18 +129,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
let key = self.key;
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::Downloads => match self.key {
|
||||
_ if key == DEFAULT_KEYBINDINGS.update.key => {
|
||||
_ if matches_key!(update, key) => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
|
||||
}
|
||||
_ if key == DEFAULT_KEYBINDINGS.refresh.key => {
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ActiveRadarrBlock::DeleteDownloadPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
|
||||
@@ -150,7 +149,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::UpdateDownloadsPrompt => {
|
||||
if key == DEFAULT_KEYBINDINGS.confirm.key {
|
||||
if matches_key!(confirm, key) {
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
|
||||
|
||||
@@ -160,4 +159,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::servarr_models::EditIndexerParams;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
use crate::{
|
||||
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_handler_tests.rs"]
|
||||
@@ -65,6 +66,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
EDIT_INDEXER_BLOCKS.contains(&active_block)
|
||||
}
|
||||
|
||||
fn ignore_special_keys(&self) -> bool {
|
||||
self.app.ignore_special_keys_for_textbox_input
|
||||
}
|
||||
|
||||
fn new(
|
||||
key: Key,
|
||||
app: &'a mut App<'b>,
|
||||
@@ -356,7 +361,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
| ActiveRadarrBlock::EditIndexerSeedRatioInput
|
||||
| ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self.app.push_navigation_stack(selected_block.into());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
self.app.ignore_special_keys_for_textbox_input = true;
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerPriorityInput => self
|
||||
.app
|
||||
@@ -402,7 +407,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
| ActiveRadarrBlock::EditIndexerSeedRatioInput
|
||||
| ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
self.app.ignore_special_keys_for_textbox_input = false;
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
|
||||
_ => (),
|
||||
@@ -423,7 +428,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
| ActiveRadarrBlock::EditIndexerPriorityInput
|
||||
| ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
self.app.ignore_special_keys_for_textbox_input = false;
|
||||
}
|
||||
_ => self.app.pop_navigation_stack(),
|
||||
}
|
||||
@@ -504,7 +509,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
&& self.key == DEFAULT_KEYBINDINGS.confirm.key
|
||||
&& matches_key!(confirm, self.key)
|
||||
{
|
||||
self.app.data.radarr_data.prompt_confirm = true;
|
||||
self.app.data.radarr_data.prompt_confirm_action =
|
||||
@@ -517,4 +522,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
self.app
|
||||
}
|
||||
|
||||
fn current_route(&self) -> crate::models::Route {
|
||||
self.app.get_current_route()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_modal_absent;
|
||||
use crate::assert_modal_present;
|
||||
use crate::assert_navigation_pushed;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
|
||||
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::servarr_models::EditIndexerParams;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
mod test_handle_scroll_up_and_down {
|
||||
@@ -17,9 +21,9 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS;
|
||||
use crate::models::BlockSelectionState;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -418,11 +422,11 @@ mod tests {
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::BlockSelectionState;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -849,9 +853,10 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::{
|
||||
servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, BlockSelectionState,
|
||||
BlockSelectionState, servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
@@ -881,10 +886,10 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Indexers.into());
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
assert!(!app.should_refresh);
|
||||
assert_eq!(app.data.radarr_data.edit_indexer_modal, None);
|
||||
assert_none!(app.data.radarr_data.edit_indexer_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -935,8 +940,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_none());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Indexers.into());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_indexer_modal);
|
||||
assert!(app.should_refresh);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
@@ -965,9 +970,9 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_some());
|
||||
assert_modal_present!(app.data.radarr_data.edit_indexer_modal);
|
||||
assert!(!app.should_refresh);
|
||||
assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
|
||||
assert_none!(app.data.radarr_data.prompt_confirm_action);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1001,8 +1006,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), block.into());
|
||||
assert!(app.should_ignore_quit_key);
|
||||
assert_navigation_pushed!(app, block.into());
|
||||
assert!(app.ignore_special_keys_for_textbox_input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1023,11 +1028,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPriorityInput.into()
|
||||
);
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert_navigation_pushed!(app, ActiveRadarrBlock::EditIndexerPriorityInput.into());
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1052,14 +1054,16 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_rss
|
||||
.unwrap());
|
||||
assert!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_rss
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
EditIndexerHandler::new(
|
||||
SUBMIT_KEY,
|
||||
@@ -1073,14 +1077,16 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_rss
|
||||
.unwrap());
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_rss
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1105,14 +1111,16 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_automatic_search
|
||||
.unwrap());
|
||||
assert!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_automatic_search
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
EditIndexerHandler::new(
|
||||
SUBMIT_KEY,
|
||||
@@ -1126,14 +1134,16 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_automatic_search
|
||||
.unwrap());
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_automatic_search
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1158,14 +1168,16 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_interactive_search
|
||||
.unwrap());
|
||||
assert!(
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_interactive_search
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
EditIndexerHandler::new(
|
||||
SUBMIT_KEY,
|
||||
@@ -1179,21 +1191,23 @@ mod tests {
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_interactive_search
|
||||
.unwrap());
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enable_interactive_search
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_name_input_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
|
||||
name: "Test".into(),
|
||||
..EditIndexerModal::default()
|
||||
@@ -1209,27 +1223,26 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.text
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.name
|
||||
.text
|
||||
.is_empty()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_url_input_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
|
||||
url: "Test".into(),
|
||||
..EditIndexerModal::default()
|
||||
@@ -1245,27 +1258,26 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.url
|
||||
.text
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.url
|
||||
.text
|
||||
.is_empty()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_api_key_input_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
|
||||
api_key: "Test".into(),
|
||||
..EditIndexerModal::default()
|
||||
@@ -1281,27 +1293,26 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.api_key
|
||||
.text
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.api_key
|
||||
.text
|
||||
.is_empty()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_seed_ratio_input_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
|
||||
seed_ratio: "Test".into(),
|
||||
..EditIndexerModal::default()
|
||||
@@ -1317,27 +1328,26 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.text
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.text
|
||||
.is_empty()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_tags_input_submit() {
|
||||
let mut app = App::test_default();
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
|
||||
tags: "Test".into(),
|
||||
..EditIndexerModal::default()
|
||||
@@ -1353,26 +1363,26 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert!(!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.tags
|
||||
.text
|
||||
.is_empty());
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert!(
|
||||
!app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.tags
|
||||
.text
|
||||
.is_empty()
|
||||
);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
}
|
||||
}
|
||||
|
||||
mod test_handle_esc {
|
||||
use super::*;
|
||||
use crate::app::App;
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::event::Key;
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -1396,9 +1406,9 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Indexers.into());
|
||||
assert!(!app.data.radarr_data.prompt_confirm);
|
||||
assert_eq!(app.data.radarr_data.edit_indexer_modal, None);
|
||||
assert_none!(app.data.radarr_data.edit_indexer_modal);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -1417,12 +1427,12 @@ mod tests {
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
|
||||
app.push_navigation_stack(active_radarr_block.into());
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
app.should_ignore_quit_key = true;
|
||||
app.ignore_special_keys_for_textbox_input = true;
|
||||
|
||||
EditIndexerHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
|
||||
assert!(!app.should_ignore_quit_key);
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Indexers.into());
|
||||
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.edit_indexer_modal,
|
||||
Some(EditIndexerModal::default())
|
||||
@@ -1431,15 +1441,15 @@ mod tests {
|
||||
}
|
||||
|
||||
mod test_handle_key_char {
|
||||
use super::*;
|
||||
use crate::app::App;
|
||||
use crate::assert_navigation_popped;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::models::servarr_data::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS;
|
||||
use crate::models::BlockSelectionState;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_name_input_backspace() {
|
||||
let mut app = App::test_default();
|
||||
@@ -1597,7 +1607,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
None,
|
||||
@@ -1613,7 +1623,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.name
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1624,7 +1634,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
None,
|
||||
@@ -1640,7 +1650,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.url
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1651,7 +1661,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
None,
|
||||
@@ -1667,7 +1677,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.api_key
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1678,7 +1688,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
None,
|
||||
@@ -1694,7 +1704,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1705,7 +1715,7 @@ mod tests {
|
||||
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
|
||||
|
||||
EditIndexerHandler::new(
|
||||
Key::Char('h'),
|
||||
Key::Char('a'),
|
||||
&mut app,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
None,
|
||||
@@ -1721,7 +1731,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.tags
|
||||
.text,
|
||||
"h"
|
||||
"a"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1772,8 +1782,8 @@ mod tests {
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_none());
|
||||
assert_navigation_popped!(app, ActiveRadarrBlock::Indexers.into());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_indexer_modal);
|
||||
assert!(app.should_refresh);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
@@ -1793,6 +1803,25 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_indexer_handler_ignore_special_keys(
|
||||
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||
) {
|
||||
let mut app = App::test_default();
|
||||
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||
let handler = EditIndexerHandler::new(
|
||||
DEFAULT_KEYBINDINGS.esc.key,
|
||||
&mut app,
|
||||
ActiveRadarrBlock::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
handler.ignore_special_keys(),
|
||||
ignore_special_keys_for_textbox_input
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_edit_indexer_params() {
|
||||
let mut app = App::test_default();
|
||||
@@ -1832,7 +1861,7 @@ mod tests {
|
||||
.build_edit_indexer_params();
|
||||
|
||||
assert_eq!(edit_indexer_params, expected_edit_indexer_params);
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_none());
|
||||
assert_modal_absent!(app.data.radarr_data.edit_indexer_modal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user