feat(accounts): add precise service filtering
Add an explicit service selector to the account creation dialog while keeping the text field focused on access types such as API and web login. Preserve the result area dimensions during filtering and allow Enter to choose the first visible account type. Cover filtering, keyboard selection, translations, and stable list geometry with focused regression tests.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildConfiguredProviderOrder,
|
||||
filterAccountDialogOptions,
|
||||
getAccountDialogSelectableOptions,
|
||||
isAccountRowSelectionKey,
|
||||
matchesAccountModeFilter,
|
||||
@@ -22,6 +23,19 @@ describe("account mode filter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("account dialog filter", () => {
|
||||
const options = [
|
||||
{ id: "rd-api", serviceLabel: "Real-Debrid", title: "Real-Debrid API", modeLabel: "API", pickerDescription: "API-Token" },
|
||||
{ id: "rd-web", serviceLabel: "Real-Debrid", title: "Real-Debrid Web-Login", modeLabel: "Web-Login", pickerDescription: "Browserfenster" },
|
||||
{ id: "md-api", serviceLabel: "Mega-Debrid", title: "Mega-Debrid API", modeLabel: "API", pickerDescription: "Login:Passwort" }
|
||||
];
|
||||
|
||||
it("combines an exact service choice with an access-type search", () => {
|
||||
expect(filterAccountDialogOptions(options, "web", "Real-Debrid").map((option) => option.id)).toEqual(["rd-web"]);
|
||||
expect(filterAccountDialogOptions(options, "api", "all").map((option) => option.id)).toEqual(["rd-api", "md-api"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("account provider order", () => {
|
||||
it("preserves the custom provider order while accounts are disabled", () => {
|
||||
expect(buildConfiguredProviderOrder(
|
||||
|
||||
@@ -885,7 +885,7 @@ describe("account workspace", () => {
|
||||
<AccountAddDialog
|
||||
actions={{
|
||||
onQueryChange: () => {},
|
||||
onFilterChange: () => {},
|
||||
onServiceFilterChange: () => {},
|
||||
onOptionSelect: () => {},
|
||||
onFieldChange: () => {},
|
||||
onClose: () => {},
|
||||
@@ -894,7 +894,8 @@ describe("account workspace", () => {
|
||||
model={{
|
||||
open: true,
|
||||
query: "",
|
||||
filter: "all",
|
||||
serviceFilter: "all",
|
||||
serviceFilters: ["Real-Debrid", "DDownload", "Mega-Debrid", "Debrid-Link"],
|
||||
options,
|
||||
selectedOptionId: "megadebrid-api",
|
||||
fields: [
|
||||
@@ -938,7 +939,9 @@ describe("account workspace", () => {
|
||||
|
||||
expect(addHtml).toContain("Account hinzufügen");
|
||||
expect(addHtml).toContain("Prüfen und speichern");
|
||||
expect(count(addHtml, "<select")).toBe(0);
|
||||
expect(count(addHtml, "<select")).toBe(1);
|
||||
expect(addHtml).toContain('aria-label="Dienst filtern"');
|
||||
expect(addHtml).toContain("Alle Dienste");
|
||||
expect(addHtml).toContain('aria-label="Dienst oder Zugangstyp suchen"');
|
||||
expect(addHtml).toContain('role="listbox"');
|
||||
expect(addHtml).toContain('class="settings-account-picker-header"');
|
||||
@@ -970,7 +973,7 @@ describe("account workspace", () => {
|
||||
const tree = AccountAddDialog({
|
||||
actions: {
|
||||
onQueryChange: () => {},
|
||||
onFilterChange: () => {},
|
||||
onServiceFilterChange: () => {},
|
||||
onOptionSelect: (optionId) => selected.push(optionId),
|
||||
onFieldChange: () => {},
|
||||
onClose: () => {},
|
||||
@@ -979,7 +982,8 @@ describe("account workspace", () => {
|
||||
model: {
|
||||
open: true,
|
||||
query: "",
|
||||
filter: "all",
|
||||
serviceFilter: "all",
|
||||
serviceFilters: ["Real-Debrid", "DDownload", "Mega-Debrid", "Debrid-Link"],
|
||||
options: accountOptions(),
|
||||
selectedOptionId: "megadebrid-api",
|
||||
fields: [],
|
||||
@@ -994,12 +998,47 @@ describe("account workspace", () => {
|
||||
expect(selected).toEqual(["debridlink-api"]);
|
||||
});
|
||||
|
||||
it("filters by service and selects the first visible option with Enter", () => {
|
||||
const selected: string[] = [];
|
||||
const serviceFilters: string[] = [];
|
||||
const options = accountOptions().filter((option) => option.title === "Mega-Debrid");
|
||||
const tree = AccountAddDialog({
|
||||
actions: {
|
||||
onQueryChange: () => {},
|
||||
onServiceFilterChange: (service) => serviceFilters.push(service),
|
||||
onOptionSelect: (optionId) => selected.push(optionId),
|
||||
onFieldChange: () => {},
|
||||
onClose: () => {},
|
||||
onSubmit: () => {}
|
||||
},
|
||||
model: {
|
||||
open: true,
|
||||
query: "api",
|
||||
serviceFilter: "Mega-Debrid",
|
||||
serviceFilters: ["Real-Debrid", "Mega-Debrid"],
|
||||
options,
|
||||
selectedOptionId: options[0].id,
|
||||
fields: [],
|
||||
error: "",
|
||||
busy: false
|
||||
}
|
||||
});
|
||||
const serviceSelect = findElement(tree, (element) => element.props["aria-label"] === "Dienst filtern");
|
||||
const search = findElement(tree, (element) => element.props["aria-label"] === "Dienst oder Zugangstyp suchen");
|
||||
|
||||
serviceSelect.props.onChange({ target: { value: "Real-Debrid" } });
|
||||
search.props.onKeyDown({ key: "Enter", preventDefault: () => selected.push("prevented") });
|
||||
|
||||
expect(serviceFilters).toEqual(["Real-Debrid"]);
|
||||
expect(selected).toEqual(["prevented", "megadebrid-api"]);
|
||||
});
|
||||
|
||||
it("shows an accessible empty result when account search has no matches", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<AccountAddDialog
|
||||
actions={{
|
||||
onQueryChange: () => {},
|
||||
onFilterChange: () => {},
|
||||
onServiceFilterChange: () => {},
|
||||
onOptionSelect: () => {},
|
||||
onFieldChange: () => {},
|
||||
onClose: () => {},
|
||||
@@ -1008,7 +1047,8 @@ describe("account workspace", () => {
|
||||
model={{
|
||||
open: true,
|
||||
query: "nicht vorhanden",
|
||||
filter: "all",
|
||||
serviceFilter: "all",
|
||||
serviceFilters: ["Real-Debrid"],
|
||||
options: [],
|
||||
selectedOptionId: null,
|
||||
fields: [],
|
||||
@@ -1149,6 +1189,8 @@ describe("settings geometry", () => {
|
||||
|
||||
it("keeps the specified form, table, switch, overflow and selection geometry", () => {
|
||||
const css = readFileSync(new URL("../src/renderer/views/settings/settings.css", import.meta.url), "utf8");
|
||||
expect(css).toMatch(/\.settings-account-picker-list\s*{[^}]*height:\s*190px;[^}]*scrollbar-gutter:\s*stable;/s);
|
||||
expect(css).toMatch(/\.settings-account-picker-empty\s*{[^}]*height:\s*190px;/s);
|
||||
expect(css).toMatch(/\.settings-content\s*{[^}]*padding:\s*24px;/s);
|
||||
expect(css).toMatch(
|
||||
/\.md-runtime-view-content\s*>\s*\.settings-content\s*{[^}]*height:\s*100%;[^}]*padding:\s*24px;/s
|
||||
|
||||
Reference in New Issue
Block a user