mirror of
https://github.com/morpheus65535/bazarr.git
synced 2025-06-27 17:09:34 -04:00
no log: Improve frontend tests (#2827)
This commit is contained in:
parent
cce50b2e69
commit
7cb987f55d
46 changed files with 1658 additions and 720 deletions
1111
frontend/package-lock.json
generated
1111
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -59,6 +59,7 @@
|
|||
"husky": "^9.0.11",
|
||||
"jsdom": "^26.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"msw": "^2.7.0",
|
||||
"postcss-preset-mantine": "^1.14.4",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
|
@ -67,7 +68,7 @@
|
|||
"recharts": "^2.15.0",
|
||||
"sass-embedded": "^1.86.1",
|
||||
"typescript": "^5.4.4",
|
||||
"vite": "6.2.4",
|
||||
"vite": "^6.3.2",
|
||||
"vite-plugin-checker": "^0.6.4",
|
||||
"vite-plugin-pwa": "^1.0.0",
|
||||
"vitest": "^3.1.1",
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { describe, it } from "vitest";
|
||||
import { render } from "@/tests";
|
||||
import { customRender } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import App from ".";
|
||||
|
||||
describe("App", () => {
|
||||
it("should render without crash", () => {
|
||||
render(<App />);
|
||||
server.use(
|
||||
http.get("/api/system/searches", () => {
|
||||
return HttpResponse.json({});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<App />);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { describe, it } from "vitest";
|
||||
import { Search } from "@/components/index";
|
||||
import { render } from "@/tests";
|
||||
import { customRender } from "@/tests";
|
||||
|
||||
describe("Search Bar", () => {
|
||||
it.skip("should render the closed empty state", () => {
|
||||
render(<Search />);
|
||||
customRender(<Search />);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,6 @@ type MutateActionProps<DATA, VAR> = Omit<
|
|||
args: () => VAR | null;
|
||||
onSuccess?: (args: DATA) => void;
|
||||
onError?: () => void;
|
||||
noReset?: boolean;
|
||||
};
|
||||
|
||||
function MutateAction<DATA, VAR>({
|
||||
|
|
|
@ -10,7 +10,6 @@ type MutateButtonProps<DATA, VAR> = Omit<
|
|||
args: () => VAR | null;
|
||||
onSuccess?: (args: DATA) => void;
|
||||
onError?: () => void;
|
||||
noReset?: boolean;
|
||||
};
|
||||
|
||||
function MutateButton<DATA, VAR>({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import { Language } from ".";
|
||||
|
||||
describe("Language text", () => {
|
||||
|
@ -9,13 +9,13 @@ describe("Language text", () => {
|
|||
};
|
||||
|
||||
it("should show short text", () => {
|
||||
render(<Language.Text value={testLanguage}></Language.Text>);
|
||||
customRender(<Language.Text value={testLanguage}></Language.Text>);
|
||||
|
||||
expect(screen.getByText(testLanguage.code2)).toBeDefined();
|
||||
});
|
||||
|
||||
it("should show long text", () => {
|
||||
render(<Language.Text value={testLanguage} long></Language.Text>);
|
||||
customRender(<Language.Text value={testLanguage} long></Language.Text>);
|
||||
|
||||
expect(screen.getByText(testLanguage.name)).toBeDefined();
|
||||
});
|
||||
|
@ -23,7 +23,7 @@ describe("Language text", () => {
|
|||
const testLanguageWithHi: Language.Info = { ...testLanguage, hi: true };
|
||||
|
||||
it("should show short text with HI", () => {
|
||||
render(<Language.Text value={testLanguageWithHi}></Language.Text>);
|
||||
customRender(<Language.Text value={testLanguageWithHi}></Language.Text>);
|
||||
|
||||
const expectedText = `${testLanguageWithHi.code2}:HI`;
|
||||
|
||||
|
@ -31,7 +31,9 @@ describe("Language text", () => {
|
|||
});
|
||||
|
||||
it("should show long text with HI", () => {
|
||||
render(<Language.Text value={testLanguageWithHi} long></Language.Text>);
|
||||
customRender(
|
||||
<Language.Text value={testLanguageWithHi} long></Language.Text>,
|
||||
);
|
||||
|
||||
const expectedText = `${testLanguageWithHi.name} HI`;
|
||||
|
||||
|
@ -44,7 +46,9 @@ describe("Language text", () => {
|
|||
};
|
||||
|
||||
it("should show short text with Forced", () => {
|
||||
render(<Language.Text value={testLanguageWithForced}></Language.Text>);
|
||||
customRender(
|
||||
<Language.Text value={testLanguageWithForced}></Language.Text>,
|
||||
);
|
||||
|
||||
const expectedText = `${testLanguageWithHi.code2}:Forced`;
|
||||
|
||||
|
@ -52,7 +56,9 @@ describe("Language text", () => {
|
|||
});
|
||||
|
||||
it("should show long text with Forced", () => {
|
||||
render(<Language.Text value={testLanguageWithForced} long></Language.Text>);
|
||||
customRender(
|
||||
<Language.Text value={testLanguageWithForced} long></Language.Text>,
|
||||
);
|
||||
|
||||
const expectedText = `${testLanguageWithHi.name} Forced`;
|
||||
|
||||
|
@ -73,7 +79,7 @@ describe("Language list", () => {
|
|||
];
|
||||
|
||||
it("should show all languages", () => {
|
||||
render(<Language.List value={elements}></Language.List>);
|
||||
customRender(<Language.List value={elements}></Language.List>);
|
||||
|
||||
elements.forEach((value) => {
|
||||
expect(screen.getByText(value.name)).toBeDefined();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { faStickyNote } from "@fortawesome/free-regular-svg-icons";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, vitest } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import Action from "./Action";
|
||||
|
||||
const testLabel = "Test Label";
|
||||
|
@ -9,7 +9,7 @@ const testIcon = faStickyNote;
|
|||
|
||||
describe("Action button", () => {
|
||||
it("should be a button", () => {
|
||||
render(<Action icon={testIcon} label={testLabel}></Action>);
|
||||
customRender(<Action icon={testIcon} label={testLabel}></Action>);
|
||||
const element = screen.getByRole("button", { name: testLabel });
|
||||
|
||||
expect(element.getAttribute("type")).toEqual("button");
|
||||
|
@ -17,7 +17,7 @@ describe("Action button", () => {
|
|||
});
|
||||
|
||||
it("should show icon", () => {
|
||||
render(<Action icon={testIcon} label={testLabel}></Action>);
|
||||
customRender(<Action icon={testIcon} label={testLabel}></Action>);
|
||||
// TODO: use getBy...
|
||||
const element = screen.getByRole("img", { hidden: true });
|
||||
|
||||
|
@ -27,7 +27,7 @@ describe("Action button", () => {
|
|||
|
||||
it("should call on-click event when clicked", async () => {
|
||||
const onClickFn = vitest.fn();
|
||||
render(
|
||||
customRender(
|
||||
<Action icon={testIcon} label={testLabel} onClick={onClickFn}></Action>,
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, vitest } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import ChipInput from "./ChipInput";
|
||||
|
||||
describe("ChipInput", () => {
|
||||
|
@ -8,7 +8,7 @@ describe("ChipInput", () => {
|
|||
|
||||
// TODO: Support default value
|
||||
it.skip("should works with default value", () => {
|
||||
render(<ChipInput defaultValue={existedValues}></ChipInput>);
|
||||
customRender(<ChipInput defaultValue={existedValues}></ChipInput>);
|
||||
|
||||
existedValues.forEach((value) => {
|
||||
expect(screen.getByText(value)).toBeDefined();
|
||||
|
@ -16,7 +16,7 @@ describe("ChipInput", () => {
|
|||
});
|
||||
|
||||
it("should works with value", () => {
|
||||
render(<ChipInput value={existedValues}></ChipInput>);
|
||||
customRender(<ChipInput value={existedValues}></ChipInput>);
|
||||
|
||||
existedValues.forEach((value) => {
|
||||
expect(screen.getByText(value)).toBeDefined();
|
||||
|
@ -29,7 +29,9 @@ describe("ChipInput", () => {
|
|||
expect(values).toContain(typedValue);
|
||||
});
|
||||
|
||||
render(<ChipInput value={existedValues} onChange={mockedFn}></ChipInput>);
|
||||
customRender(
|
||||
<ChipInput value={existedValues} onChange={mockedFn}></ChipInput>,
|
||||
);
|
||||
|
||||
const element = screen.getByRole("searchbox");
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, vitest } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import { Selector, SelectorOption } from "./Selector";
|
||||
|
||||
const selectorName = "Test Selections";
|
||||
|
@ -18,7 +18,9 @@ const testOptions: SelectorOption<string>[] = [
|
|||
describe("Selector", () => {
|
||||
describe("options", () => {
|
||||
it("should work with the SelectorOption", () => {
|
||||
render(<Selector name={selectorName} options={testOptions}></Selector>);
|
||||
customRender(
|
||||
<Selector name={selectorName} options={testOptions}></Selector>,
|
||||
);
|
||||
|
||||
testOptions.forEach((o) => {
|
||||
expect(screen.getByText(o.label)).toBeDefined();
|
||||
|
@ -26,7 +28,9 @@ describe("Selector", () => {
|
|||
});
|
||||
|
||||
it("should display when clicked", async () => {
|
||||
render(<Selector name={selectorName} options={testOptions}></Selector>);
|
||||
customRender(
|
||||
<Selector name={selectorName} options={testOptions}></Selector>,
|
||||
);
|
||||
|
||||
const element = screen.getByTestId("input-selector");
|
||||
|
||||
|
@ -41,7 +45,7 @@ describe("Selector", () => {
|
|||
|
||||
it("shouldn't show default value", async () => {
|
||||
const option = testOptions[0];
|
||||
render(
|
||||
customRender(
|
||||
<Selector
|
||||
name={selectorName}
|
||||
options={testOptions}
|
||||
|
@ -54,7 +58,7 @@ describe("Selector", () => {
|
|||
|
||||
it("shouldn't show value", async () => {
|
||||
const option = testOptions[0];
|
||||
render(
|
||||
customRender(
|
||||
<Selector
|
||||
name={selectorName}
|
||||
options={testOptions}
|
||||
|
@ -72,7 +76,7 @@ describe("Selector", () => {
|
|||
const mockedFn = vitest.fn((value: string | null) => {
|
||||
expect(value).toEqual(clickedOption.value);
|
||||
});
|
||||
render(
|
||||
customRender(
|
||||
<Selector
|
||||
name={selectorName}
|
||||
options={testOptions}
|
||||
|
@ -112,7 +116,7 @@ describe("Selector", () => {
|
|||
const mockedFn = vitest.fn((value: { name: string } | null) => {
|
||||
expect(value).toEqual(clickedOption.value);
|
||||
});
|
||||
render(
|
||||
customRender(
|
||||
<Selector
|
||||
name={selectorName}
|
||||
options={objectOptions}
|
||||
|
@ -134,7 +138,7 @@ describe("Selector", () => {
|
|||
describe("placeholder", () => {
|
||||
it("should show when no selection", () => {
|
||||
const placeholder = "Empty Selection";
|
||||
render(
|
||||
customRender(
|
||||
<Selector
|
||||
name={selectorName}
|
||||
options={testOptions}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, it } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import Authentication from "./Authentication";
|
||||
|
||||
describe("Authentication", () => {
|
||||
it("should render without crash", () => {
|
||||
render(<Authentication></Authentication>);
|
||||
customRender(<Authentication></Authentication>);
|
||||
|
||||
expect(screen.getByPlaceholderText("Username")).toBeDefined();
|
||||
expect(screen.getByPlaceholderText("Password")).toBeDefined();
|
||||
|
|
71
frontend/src/pages/Blacklist/Movies/index.test.tsx
Normal file
71
frontend/src/pages/Blacklist/Movies/index.test.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* eslint-disable camelcase */
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import BlacklistMoviesView from ".";
|
||||
|
||||
describe("Blacklist Movies", () => {
|
||||
it("should render with blacklisted movies", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/settings", () => {
|
||||
return HttpResponse.json({
|
||||
general: {
|
||||
theme: "auto",
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
server.use(
|
||||
http.get("/api/movies/blacklist", () => {
|
||||
return HttpResponse.json({
|
||||
data: [
|
||||
{
|
||||
title: "Batman vs Teenage Mutant Ninja Turtles",
|
||||
radarrId: 50,
|
||||
provider: "yifysubtitles",
|
||||
subs_id:
|
||||
"https://yifysubtitles.ch/subtitles/batman-vs-teenage-mutant-ninja-turtles-2019-english-yify-19252",
|
||||
language: {
|
||||
name: "English",
|
||||
code2: "en",
|
||||
code3: "eng",
|
||||
forced: false,
|
||||
hi: false,
|
||||
},
|
||||
timestamp: "28 seconds ago",
|
||||
parsed_timestamp: "01/23/25 05:39:36",
|
||||
},
|
||||
],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<BlacklistMoviesView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("yifysubtitles")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render without blacklisted movies", async () => {
|
||||
server.use(
|
||||
http.get("/api/movies/blacklist", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<BlacklistMoviesView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("No blacklisted movies subtitles"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
server.resetHandlers();
|
||||
});
|
||||
});
|
|
@ -82,7 +82,6 @@ const Table: FunctionComponent<Props> = ({ blacklist }) => {
|
|||
return (
|
||||
<MutateAction
|
||||
label="Remove from Blacklist"
|
||||
noReset
|
||||
icon={faTrash}
|
||||
mutation={remove}
|
||||
args={() => ({
|
||||
|
|
62
frontend/src/pages/Blacklist/Series/index.test.tsx
Normal file
62
frontend/src/pages/Blacklist/Series/index.test.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
/* eslint-disable camelcase */
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import BlacklistSeriesView from ".";
|
||||
|
||||
describe("Blacklist Series", () => {
|
||||
it("should render without blacklisted series", async () => {
|
||||
server.use(
|
||||
http.get("/api/episodes/blacklist", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<BlacklistSeriesView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("No blacklisted series subtitles"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render with blacklisted series", async () => {
|
||||
server.use(
|
||||
http.get("/api/episodes/blacklist", () => {
|
||||
// TODO: Replace with Factory
|
||||
return HttpResponse.json({
|
||||
data: [
|
||||
{
|
||||
seriesTitle: "Dragon Ball DAIMA",
|
||||
episode_number: "1x14",
|
||||
episodeTitle: "Taboo",
|
||||
sonarrSeriesId: 56,
|
||||
provider: "animetosho",
|
||||
subs_id:
|
||||
"https://animetosho.org/storage/attach/0022fd50/2293072.xz",
|
||||
language: {
|
||||
name: "English",
|
||||
code2: "en",
|
||||
code3: "eng",
|
||||
forced: false,
|
||||
hi: false,
|
||||
},
|
||||
timestamp: "now",
|
||||
parsed_timestamp: "01/24/25 01:38:03",
|
||||
},
|
||||
],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<BlacklistSeriesView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("animetosho")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -89,7 +89,6 @@ const Table: FunctionComponent<Props> = ({ blacklist }) => {
|
|||
return (
|
||||
<MutateAction
|
||||
label="Remove from Blacklist"
|
||||
noReset
|
||||
icon={faTrash}
|
||||
mutation={removeFromBlacklist}
|
||||
args={() => ({
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { renderTest, RenderTestCase } from "@/tests/render";
|
||||
import BlacklistMoviesView from "./Movies";
|
||||
import BlacklistSeriesView from "./Series";
|
||||
|
||||
const cases: RenderTestCase[] = [
|
||||
{
|
||||
name: "movie page",
|
||||
ui: BlacklistMoviesView,
|
||||
},
|
||||
{
|
||||
name: "series page",
|
||||
ui: BlacklistSeriesView,
|
||||
},
|
||||
];
|
||||
|
||||
renderTest("Blacklist", cases);
|
127
frontend/src/pages/History/Movies/index.test.tsx
Normal file
127
frontend/src/pages/History/Movies/index.test.tsx
Normal file
|
@ -0,0 +1,127 @@
|
|||
/* eslint-disable camelcase */
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import MoviesHistoryView from ".";
|
||||
|
||||
const mockMovieHistory = {
|
||||
data: [
|
||||
{
|
||||
action: "download",
|
||||
title: "The Dark Knight (2008) [1080p.BluRay.x264.DTS-HD.MA.5.1]",
|
||||
radarrId: 1,
|
||||
language: { code2: "en", name: "English" },
|
||||
score: 0.95,
|
||||
matches: ["scene", "release"],
|
||||
dont_matches: [],
|
||||
timestamp: "2024-03-20T10:00:00Z",
|
||||
parsed_timestamp: "March 20, 2024 10:00:00",
|
||||
description: "Test description",
|
||||
upgradable: true,
|
||||
blacklisted: false,
|
||||
provider: "opensubtitles",
|
||||
subs_id: "123",
|
||||
subtitles_path: "/path/to/subtitles.srt",
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
};
|
||||
|
||||
describe("History Movies", () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
http.get("/api/movies/history", () => {
|
||||
return HttpResponse.json(mockMovieHistory);
|
||||
}),
|
||||
http.get("/api/providers", () => {
|
||||
return HttpResponse.json({
|
||||
data: ["test-provider"],
|
||||
});
|
||||
}),
|
||||
http.get("/api/system/languages", () => {
|
||||
return HttpResponse.json({
|
||||
en: { code2: "en", name: "English" },
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render the movies history table", async () => {
|
||||
customRender(<MoviesHistoryView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
"The Dark Knight (2008) [1080p.BluRay.x264.DTS-HD.MA.5.1]",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Name")).toBeInTheDocument();
|
||||
expect(screen.getByText("Language")).toBeInTheDocument();
|
||||
expect(screen.getByText("Score")).toBeInTheDocument();
|
||||
expect(screen.getByText("Match")).toBeInTheDocument();
|
||||
expect(screen.getByText("Date")).toBeInTheDocument();
|
||||
expect(screen.getByText("Info")).toBeInTheDocument();
|
||||
expect(screen.getByText("Upgradable")).toBeInTheDocument();
|
||||
expect(screen.getByText("Blacklist")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display movie information correctly", async () => {
|
||||
customRender(<MoviesHistoryView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
"The Dark Knight (2008) [1080p.BluRay.x264.DTS-HD.MA.5.1]",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("English")).toBeInTheDocument();
|
||||
expect(screen.getByText("0.95")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show blacklist button when movie is not blacklisted", async () => {
|
||||
customRender(<MoviesHistoryView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText("Add to Blacklist")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show empty state when no history is found", async () => {
|
||||
server.use(
|
||||
http.get("/api/movies/history", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<MoviesHistoryView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("Nothing Found in Movies History"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should navigate to movie details when clicking on movie title", async () => {
|
||||
customRender(<MoviesHistoryView />);
|
||||
|
||||
await waitFor(() => {
|
||||
const movieLink = screen.getByText(
|
||||
"The Dark Knight (2008) [1080p.BluRay.x264.DTS-HD.MA.5.1]",
|
||||
);
|
||||
expect(movieLink).toHaveAttribute("href", "/movies/1");
|
||||
});
|
||||
});
|
||||
});
|
48
frontend/src/pages/History/Series/index.test.tsx
Normal file
48
frontend/src/pages/History/Series/index.test.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SeriesHistoryView from ".";
|
||||
|
||||
describe("History Series", () => {
|
||||
it("should render with series", async () => {
|
||||
server.use(
|
||||
http.get("/api/episodes/history", () => {
|
||||
return HttpResponse.json({
|
||||
data: [
|
||||
{
|
||||
seriesTitle: "Breaking Bad",
|
||||
episode_number: "S05E07",
|
||||
episodeTitle: "Pilot",
|
||||
language: { code2: "en", name: "English" },
|
||||
action: "download",
|
||||
timestamp: "2023-05-10",
|
||||
parsed_timestamp: "May 10, 2023",
|
||||
sonarrSeriesId: 123,
|
||||
sonarrEpisodeId: 456,
|
||||
description: "Test description",
|
||||
score: 100,
|
||||
matches: [],
|
||||
dont_matches: [],
|
||||
upgradable: false,
|
||||
blacklisted: false,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
totalItems: 1,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SeriesHistoryView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Breaking Bad")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("S05E07")).toBeInTheDocument();
|
||||
});
|
||||
});
|
37
frontend/src/pages/History/Statistics/HistoryStats.test.tsx
Normal file
37
frontend/src/pages/History/Statistics/HistoryStats.test.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import HistoryStats from "./HistoryStats";
|
||||
|
||||
describe("History Stats", () => {
|
||||
it("should render without stats", async () => {
|
||||
server.use(
|
||||
http.get("/api/providers", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
server.use(
|
||||
http.get("/api/system/languages", () => {
|
||||
return HttpResponse.json({});
|
||||
}),
|
||||
);
|
||||
server.use(
|
||||
http.get("/api/history/stats", () => {
|
||||
return HttpResponse.json({
|
||||
series: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
server.use(
|
||||
http.get("/api/system/providers", () => {
|
||||
return HttpResponse.json({});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<HistoryStats />);
|
||||
});
|
||||
});
|
|
@ -1,21 +0,0 @@
|
|||
import { renderTest, RenderTestCase } from "@/tests/render";
|
||||
import HistoryStats from "./Statistics/HistoryStats";
|
||||
import MoviesHistoryView from "./Movies";
|
||||
import SeriesHistoryView from "./Series";
|
||||
|
||||
const cases: RenderTestCase[] = [
|
||||
{
|
||||
name: "movie page",
|
||||
ui: MoviesHistoryView,
|
||||
},
|
||||
{
|
||||
name: "series page",
|
||||
ui: SeriesHistoryView,
|
||||
},
|
||||
{
|
||||
name: "statistics page",
|
||||
ui: HistoryStats,
|
||||
},
|
||||
];
|
||||
|
||||
renderTest("History", cases);
|
|
@ -1,16 +1,46 @@
|
|||
import { describe } from "vitest";
|
||||
import { render } from "@/tests";
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { beforeEach, describe, it } from "vitest";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import MovieMassEditor from "./Editor";
|
||||
import MovieView from ".";
|
||||
|
||||
describe("Movies page", () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
http.get("/api/movies", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
render(<MovieView />);
|
||||
customRender(<MovieView />);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Movies editor page", () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
http.get("/api/movies", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
server.use(
|
||||
http.get("/api/system/languages/profiles", () => {
|
||||
return HttpResponse.json([]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
render(<MovieMassEditor />);
|
||||
customRender(<MovieMassEditor />);
|
||||
|
||||
expect(screen.getByText("Actions")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,16 +1,44 @@
|
|||
import { describe } from "vitest";
|
||||
import { render } from "@/tests";
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { beforeEach, describe, it } from "vitest";
|
||||
import { customRender } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SeriesMassEditor from "./Editor";
|
||||
import SeriesView from ".";
|
||||
|
||||
describe("Series page", () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
http.get("/api/series", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
render(<SeriesView />);
|
||||
customRender(<SeriesView />);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Series editor page", () => {
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
http.get("/api/series", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
server.use(
|
||||
http.get("/api/system/languages/profiles", () => {
|
||||
return HttpResponse.json([]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
render(<SeriesMassEditor />);
|
||||
customRender(<SeriesMassEditor />);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Text } from "@mantine/core";
|
||||
import { describe, it } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import Layout from "./Layout";
|
||||
|
||||
describe("Settings layout", () => {
|
||||
it.concurrent("should be able to render without issues", () => {
|
||||
render(
|
||||
customRender(
|
||||
<Layout name="Test Settings">
|
||||
<Text>Value</Text>
|
||||
</Layout>,
|
||||
|
@ -13,7 +13,7 @@ describe("Settings layout", () => {
|
|||
});
|
||||
|
||||
it.concurrent("save button should be disabled by default", () => {
|
||||
render(
|
||||
customRender(
|
||||
<Layout name="Test Settings">
|
||||
<Text>Value</Text>
|
||||
</Layout>,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Text } from "@mantine/core";
|
||||
import { describe, it } from "vitest";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import { Section } from "./Section";
|
||||
|
||||
describe("Settings section", () => {
|
||||
const header = "Section Header";
|
||||
|
||||
it("should show header", () => {
|
||||
render(<Section header="Section Header"></Section>);
|
||||
customRender(<Section header="Section Header"></Section>);
|
||||
|
||||
expect(screen.getByText(header)).toBeDefined();
|
||||
expect(screen.getByRole("separator")).toBeDefined();
|
||||
|
@ -14,7 +15,7 @@ describe("Settings section", () => {
|
|||
|
||||
it("should show children", () => {
|
||||
const text = "Section Child";
|
||||
render(
|
||||
customRender(
|
||||
<Section header="Section Header">
|
||||
<Text>{text}</Text>
|
||||
</Section>,
|
||||
|
@ -26,7 +27,7 @@ describe("Settings section", () => {
|
|||
|
||||
it("should work with hidden", () => {
|
||||
const text = "Section Child";
|
||||
render(
|
||||
customRender(
|
||||
<Section header="Section Header" hidden>
|
||||
<Text>{text}</Text>
|
||||
</Section>,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { FunctionComponent, PropsWithChildren, ReactElement } from "react";
|
|||
import { useForm } from "@mantine/form";
|
||||
import { describe, it } from "vitest";
|
||||
import { FormContext, FormValues } from "@/pages/Settings/utilities/FormValues";
|
||||
import { render, screen } from "@/tests";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import { Number, Text } from "./forms";
|
||||
|
||||
const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
|
@ -16,7 +16,7 @@ const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
|||
};
|
||||
|
||||
const formRender = (ui: ReactElement) =>
|
||||
render(<FormSupport>{ui}</FormSupport>);
|
||||
customRender(<FormSupport>{ui}</FormSupport>);
|
||||
|
||||
describe("Settings form", () => {
|
||||
describe("number component", () => {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import server from "@/tests/mocks/node";
|
||||
import { renderTest, RenderTestCase } from "@/tests/render";
|
||||
import SettingsGeneralView from "./General";
|
||||
import SettingsLanguagesView from "./Languages";
|
||||
import SettingsNotificationsView from "./Notifications";
|
||||
import SettingsProvidersView from "./Providers";
|
||||
import SettingsRadarrView from "./Radarr";
|
||||
import SettingsSchedulerView from "./Scheduler";
|
||||
import SettingsSonarrView from "./Sonarr";
|
||||
import SettingsSubtitlesView from "./Subtitles";
|
||||
import SettingsUIView from "./UI";
|
||||
|
||||
|
@ -17,27 +17,32 @@ const cases: RenderTestCase[] = [
|
|||
{
|
||||
name: "languages page",
|
||||
ui: SettingsLanguagesView,
|
||||
setupEach: () => {
|
||||
server.use(
|
||||
http.get("/api/system/languages", () => {
|
||||
return HttpResponse.json({});
|
||||
}),
|
||||
);
|
||||
server.use(
|
||||
http.get("/api/system/languages/profiles", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "notifications page",
|
||||
ui: SettingsNotificationsView,
|
||||
},
|
||||
// TODO: Test Notifications Page
|
||||
{
|
||||
name: "providers page",
|
||||
ui: SettingsProvidersView,
|
||||
},
|
||||
{
|
||||
name: "radarr page",
|
||||
ui: SettingsRadarrView,
|
||||
},
|
||||
// TODO: Test Radarr Page
|
||||
{
|
||||
name: "scheduler page",
|
||||
ui: SettingsSchedulerView,
|
||||
},
|
||||
{
|
||||
name: "sonarr page",
|
||||
ui: SettingsSonarrView,
|
||||
},
|
||||
// TODO: Test Sonarr Page
|
||||
{
|
||||
name: "subtitles page",
|
||||
ui: SettingsSubtitlesView,
|
||||
|
|
67
frontend/src/pages/System/Announcements/index.test.tsx
Normal file
67
frontend/src/pages/System/Announcements/index.test.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemAnnouncementsView from ".";
|
||||
|
||||
describe("System Announcements", () => {
|
||||
it("should render with empty announcements", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/announcements", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemAnnouncementsView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/No announcements for now, come back later!/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render with announcements", async () => {
|
||||
const mockAnnouncements = [
|
||||
{
|
||||
text: "New Subtitle Provider!",
|
||||
dismissible: true,
|
||||
},
|
||||
{
|
||||
text: "Python Deprecated!",
|
||||
dismissible: false,
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
http.get("/api/system/announcements", () => {
|
||||
return HttpResponse.json({
|
||||
data: mockAnnouncements,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemAnnouncementsView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("New Subtitle Provider!")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Python Deprecated!")).toBeInTheDocument();
|
||||
|
||||
const dismissButtons = screen.getAllByLabelText("Dismiss announcement");
|
||||
|
||||
const dismissableButton = dismissButtons.find((button) =>
|
||||
button.closest("tr")?.textContent?.includes("New Subtitle Provider!"),
|
||||
);
|
||||
|
||||
const nonDismissableButton = dismissButtons.find((button) =>
|
||||
button.closest("tr")?.textContent?.includes("Python Deprecated!"),
|
||||
);
|
||||
|
||||
expect(dismissableButton).not.toBeDisabled();
|
||||
expect(nonDismissableButton).toBeDisabled();
|
||||
});
|
||||
});
|
21
frontend/src/pages/System/Backups/index.test.tsx
Normal file
21
frontend/src/pages/System/Backups/index.test.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemBackupsView from ".";
|
||||
|
||||
describe("System Backups", () => {
|
||||
it("should render with backups", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/backups", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemBackupsView />);
|
||||
|
||||
// TODO: Assert
|
||||
});
|
||||
});
|
21
frontend/src/pages/System/Logs/index.test.tsx
Normal file
21
frontend/src/pages/System/Logs/index.test.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemLogsView from ".";
|
||||
|
||||
describe("System Logs", () => {
|
||||
it("should render with logs", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/logs", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemLogsView />);
|
||||
|
||||
// TODO: Assert
|
||||
});
|
||||
});
|
55
frontend/src/pages/System/Providers/index.test.tsx
Normal file
55
frontend/src/pages/System/Providers/index.test.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemProvidersView from ".";
|
||||
|
||||
describe("System Providers", () => {
|
||||
it("should render with providers", async () => {
|
||||
server.use(
|
||||
http.get("/api/providers", () => {
|
||||
return HttpResponse.json({
|
||||
data: [
|
||||
{ name: "OpenSubtitles", status: "active", retry: "0" },
|
||||
{ name: "Subscene", status: "inactive", retry: "3" },
|
||||
{ name: "Addic7ed", status: "disabled", retry: "1" },
|
||||
],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemProvidersView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("OpenSubtitles")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("OpenSubtitles")).toBeInTheDocument();
|
||||
expect(screen.getByText("Subscene")).toBeInTheDocument();
|
||||
expect(screen.getByText("Addic7ed")).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("active")).toBeInTheDocument();
|
||||
expect(screen.getByText("inactive")).toBeInTheDocument();
|
||||
expect(screen.getByText("disabled")).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("0")).toBeInTheDocument();
|
||||
expect(screen.getByText("3")).toBeInTheDocument();
|
||||
expect(screen.getByText("1")).toBeInTheDocument();
|
||||
|
||||
// Verify toolbar buttons are present
|
||||
expect(screen.getByText("Refresh")).toBeInTheDocument();
|
||||
expect(screen.getByText("Reset")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render with no providers", async () => {
|
||||
server.use(
|
||||
http.get("/api/providers", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemProvidersView />);
|
||||
});
|
||||
});
|
79
frontend/src/pages/System/Releases/index.test.tsx
Normal file
79
frontend/src/pages/System/Releases/index.test.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemReleasesView from ".";
|
||||
|
||||
describe("System Releases", () => {
|
||||
it("should render with releases", async () => {
|
||||
const mockReleases = [
|
||||
{
|
||||
name: "v1.0.0",
|
||||
body: [
|
||||
"Added support for embedded subtitles in MKV files",
|
||||
"Improved subtitle synchronization accuracy",
|
||||
],
|
||||
date: "2024-03-20",
|
||||
prerelease: false,
|
||||
current: true,
|
||||
},
|
||||
{
|
||||
name: "v1.1.0-beta",
|
||||
body: [
|
||||
"Added support for multiple subtitle providers",
|
||||
"Enhanced subtitle language detection",
|
||||
],
|
||||
date: "2024-03-21",
|
||||
prerelease: true,
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
http.get("/api/system/releases", () => {
|
||||
return HttpResponse.json({
|
||||
data: mockReleases,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemReleasesView />);
|
||||
|
||||
await screen.findByText("v1.0.0");
|
||||
await screen.findByText("v1.1.0-beta");
|
||||
|
||||
expect(screen.getByText("v1.0.0")).toBeInTheDocument();
|
||||
expect(screen.getByText("v1.1.0-beta")).toBeInTheDocument();
|
||||
expect(screen.getByText("2024-03-20")).toBeInTheDocument();
|
||||
expect(screen.getByText("2024-03-21")).toBeInTheDocument();
|
||||
expect(screen.getByText("Master")).toBeInTheDocument();
|
||||
expect(screen.getByText("Development")).toBeInTheDocument();
|
||||
expect(screen.getByText("Installed")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Added support for embedded subtitles in MKV files"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Improved subtitle synchronization accuracy"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Added support for multiple subtitle providers"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("Enhanced subtitle language detection"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render empty state when no releases", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/releases", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemReleasesView />);
|
||||
|
||||
expect(screen.queryByRole("card")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
29
frontend/src/pages/System/Status/index.test.tsx
Normal file
29
frontend/src/pages/System/Status/index.test.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemStatusView from ".";
|
||||
|
||||
describe("System Status", () => {
|
||||
it("should render with status", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/status", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
server.use(
|
||||
http.get("/api/system/health", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemStatusView />);
|
||||
|
||||
// TODO: Assert
|
||||
});
|
||||
});
|
69
frontend/src/pages/System/Tasks/index.test.tsx
Normal file
69
frontend/src/pages/System/Tasks/index.test.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* eslint-disable camelcase */
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen, waitFor } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import SystemTasksView from ".";
|
||||
|
||||
describe("System Tasks", () => {
|
||||
it("should render without tasks", async () => {
|
||||
server.use(
|
||||
http.get("/api/system/tasks", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemTasksView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Refresh")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render with system tasks", async () => {
|
||||
const mockTasks = [
|
||||
{
|
||||
name: "Scan Series",
|
||||
interval: "1 hour",
|
||||
next_run_in: "30 minutes",
|
||||
job_id: "series_scan",
|
||||
job_running: false,
|
||||
},
|
||||
{
|
||||
name: "Scan Movies",
|
||||
interval: "1 hour",
|
||||
next_run_in: "45 minutes",
|
||||
job_id: "movies_scan",
|
||||
job_running: true,
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
http.get("/api/system/tasks", () => {
|
||||
return HttpResponse.json({
|
||||
data: mockTasks,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<SystemTasksView />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Scan Series")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Scan Movies")).toBeInTheDocument();
|
||||
expect(screen.getByText("30 minutes")).toBeInTheDocument();
|
||||
expect(screen.getByText("45 minutes")).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("Name")).toBeInTheDocument();
|
||||
expect(screen.getByText("Interval")).toBeInTheDocument();
|
||||
expect(screen.getByText("Next Execution")).toBeInTheDocument();
|
||||
expect(screen.getByText("Run")).toBeInTheDocument();
|
||||
|
||||
const runButtons = screen.getAllByLabelText("Run Job");
|
||||
expect(runButtons).toHaveLength(2);
|
||||
});
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
import SystemAnnouncementsView from "@/pages/System/Announcements";
|
||||
import { renderTest, RenderTestCase } from "@/tests/render";
|
||||
import SystemBackupsView from "./Backups";
|
||||
import SystemLogsView from "./Logs";
|
||||
import SystemProvidersView from "./Providers";
|
||||
import SystemReleasesView from "./Releases";
|
||||
import SystemStatusView from "./Status";
|
||||
import SystemTasksView from "./Tasks";
|
||||
|
||||
const cases: RenderTestCase[] = [
|
||||
{
|
||||
name: "backups page",
|
||||
ui: SystemBackupsView,
|
||||
},
|
||||
{
|
||||
name: "logs page",
|
||||
ui: SystemLogsView,
|
||||
},
|
||||
{
|
||||
name: "providers page",
|
||||
ui: SystemProvidersView,
|
||||
},
|
||||
{
|
||||
name: "releases page",
|
||||
ui: SystemReleasesView,
|
||||
},
|
||||
{
|
||||
name: "status page",
|
||||
ui: SystemStatusView,
|
||||
},
|
||||
{
|
||||
name: "tasks page",
|
||||
ui: SystemTasksView,
|
||||
},
|
||||
{
|
||||
name: "announcements page",
|
||||
ui: SystemAnnouncementsView,
|
||||
},
|
||||
];
|
||||
|
||||
renderTest("System", cases);
|
62
frontend/src/pages/Wanted/Movies/index.test.tsx
Normal file
62
frontend/src/pages/Wanted/Movies/index.test.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import WantedMoviesView from ".";
|
||||
|
||||
describe("Wanted Movies", () => {
|
||||
it("should render with wanted movies", async () => {
|
||||
const mockMovies = [
|
||||
{
|
||||
title: "The Shawshank Redemption",
|
||||
radarrId: 1,
|
||||
missing_subtitles: [
|
||||
{
|
||||
code2: "en",
|
||||
name: "English",
|
||||
hi: false,
|
||||
forced: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
http.get("/api/movies/wanted", () => {
|
||||
return HttpResponse.json({
|
||||
data: mockMovies,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<WantedMoviesView />);
|
||||
|
||||
const movieTitle = await screen.findByText("The Shawshank Redemption");
|
||||
expect(movieTitle).toBeInTheDocument();
|
||||
|
||||
const movieLink = screen.getByRole("link", {
|
||||
name: "The Shawshank Redemption",
|
||||
});
|
||||
expect(movieLink).toHaveAttribute("href", "/movies/1");
|
||||
});
|
||||
|
||||
it("should render empty state when no wanted movies", async () => {
|
||||
server.use(
|
||||
http.get("/api/movies/wanted", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<WantedMoviesView />);
|
||||
|
||||
const table = await screen.findByRole("table");
|
||||
expect(table).toBeInTheDocument();
|
||||
|
||||
const movieTitle = screen.queryByText("The Shawshank Redemption");
|
||||
expect(movieTitle).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
67
frontend/src/pages/Wanted/Series/index.test.tsx
Normal file
67
frontend/src/pages/Wanted/Series/index.test.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { customRender, screen } from "@/tests";
|
||||
import server from "@/tests/mocks/node";
|
||||
import WantedSeriesView from ".";
|
||||
|
||||
describe("Wanted Series", () => {
|
||||
it("should render with wanted series", async () => {
|
||||
const mockData = {
|
||||
data: [
|
||||
{
|
||||
sonarrSeriesId: 1,
|
||||
sonarrEpisodeId: 101,
|
||||
seriesTitle: "Breaking Bad",
|
||||
episode_number: "S01E01",
|
||||
episodeTitle: "Pilot",
|
||||
missing_subtitles: [
|
||||
{
|
||||
code2: "en",
|
||||
name: "English",
|
||||
hi: false,
|
||||
forced: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
};
|
||||
|
||||
server.use(
|
||||
http.get("/api/episodes/wanted", () => {
|
||||
return HttpResponse.json(mockData);
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<WantedSeriesView />);
|
||||
|
||||
await screen.findByText("Breaking Bad");
|
||||
expect(screen.getByText("Name")).toBeInTheDocument();
|
||||
expect(screen.getByText("Episode")).toBeInTheDocument();
|
||||
expect(screen.getByText("Missing")).toBeInTheDocument();
|
||||
expect(screen.getByText("Breaking Bad")).toBeInTheDocument();
|
||||
expect(screen.getByText("S01E01")).toBeInTheDocument();
|
||||
expect(screen.getByText("Pilot")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render empty state when no wanted series", async () => {
|
||||
server.use(
|
||||
http.get("/api/episodes/wanted", () => {
|
||||
return HttpResponse.json({
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
customRender(<WantedSeriesView />);
|
||||
|
||||
await screen.findByText(/No missing Series subtitles/i);
|
||||
});
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
import { renderTest, RenderTestCase } from "@/tests/render";
|
||||
import WantedMoviesView from "./Movies";
|
||||
import WantedSeriesView from "./Series";
|
||||
|
||||
const cases: RenderTestCase[] = [
|
||||
{
|
||||
name: "movie page",
|
||||
ui: WantedMoviesView,
|
||||
},
|
||||
{
|
||||
name: "series page",
|
||||
ui: WantedSeriesView,
|
||||
},
|
||||
];
|
||||
|
||||
renderTest("Wanted", cases);
|
|
@ -1,22 +1,22 @@
|
|||
import { render } from "@/tests";
|
||||
import { customRender } from "@/tests";
|
||||
import CriticalError from "./CriticalError";
|
||||
import NotFound from "./NotFound";
|
||||
import UIError from "./UIError";
|
||||
|
||||
describe("Not found page", () => {
|
||||
it("should display message", () => {
|
||||
render(<NotFound />);
|
||||
customRender(<NotFound />);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Critical error page", () => {
|
||||
it("should disable error", () => {
|
||||
render(<CriticalError message="Test error"></CriticalError>);
|
||||
customRender(<CriticalError message="Test error"></CriticalError>);
|
||||
});
|
||||
});
|
||||
|
||||
describe("UI error page", () => {
|
||||
it("should disable error", () => {
|
||||
render(<UIError error={new Error("Test error")}></UIError>);
|
||||
customRender(<UIError error={new Error("Test error")}></UIError>);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -57,14 +57,6 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
|
|||
];
|
||||
}, [profileOptions.options]);
|
||||
|
||||
const getKey = useCallback((value: Language.Profile | null) => {
|
||||
if (value) {
|
||||
return value.name;
|
||||
}
|
||||
|
||||
return "Clear";
|
||||
}, []);
|
||||
|
||||
const { mutateAsync } = mutation;
|
||||
|
||||
/**
|
||||
|
@ -136,7 +128,6 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) {
|
|||
placeholder="Change Profile"
|
||||
withCheckIcon={false}
|
||||
options={profileOptionsWithAction}
|
||||
getkey={getKey}
|
||||
disabled={selections.length === 0}
|
||||
comboboxProps={{
|
||||
store: combobox,
|
||||
|
|
|
@ -35,6 +35,7 @@ const customRender = (
|
|||
|
||||
// re-export everything
|
||||
export * from "@testing-library/react";
|
||||
|
||||
// override render method
|
||||
export { customRender as render };
|
||||
export { customRender };
|
||||
export { render as rawRender };
|
||||
|
|
5
frontend/src/tests/mocks/node.ts
Normal file
5
frontend/src/tests/mocks/node.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { setupServer } from "msw/node";
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
export default server;
|
|
@ -1,16 +1,23 @@
|
|||
import { FunctionComponent } from "react";
|
||||
import { render } from ".";
|
||||
import { customRender } from ".";
|
||||
|
||||
export interface RenderTestCase {
|
||||
name: string;
|
||||
ui: FunctionComponent;
|
||||
setupEach?: () => void;
|
||||
}
|
||||
|
||||
export function renderTest(name: string, cases: RenderTestCase[]) {
|
||||
describe(name, () => {
|
||||
beforeEach(() => {
|
||||
cases.forEach((element) => {
|
||||
element.setupEach?.();
|
||||
});
|
||||
});
|
||||
|
||||
cases.forEach((element) => {
|
||||
it(`${element.name.toLowerCase()} should render`, () => {
|
||||
render(<element.ui />);
|
||||
customRender(<element.ui />);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
|
||||
import { vitest } from "vitest";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
// From https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: vitest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vitest.fn(), // Deprecated
|
||||
removeListener: vitest.fn(), // Deprecated
|
||||
addEventListener: vitest.fn(),
|
||||
removeEventListener: vitest.fn(),
|
||||
dispatchEvent: vitest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
// From https://github.com/mantinedev/mantine/blob/master/configuration/jest/jsdom.mocks.js
|
||||
class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
window.ResizeObserver = ResizeObserver;
|
||||
|
||||
window.scrollTo = () => {};
|
70
frontend/src/tests/setup.tsx
Normal file
70
frontend/src/tests/setup.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
|
||||
import { http } from "msw";
|
||||
import { HttpResponse } from "msw";
|
||||
import { vi, vitest } from "vitest";
|
||||
import "@testing-library/jest-dom";
|
||||
import queryClient from "@/apis/queries";
|
||||
import server from "./mocks/node";
|
||||
|
||||
vi.mock("recharts", async () => {
|
||||
const OriginalRechartsModule = await vi.importActual("recharts");
|
||||
|
||||
return {
|
||||
...OriginalRechartsModule,
|
||||
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
|
||||
<div style={{ width: "100%", height: "100%" }}>{children}</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
// From https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: vitest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vitest.fn(), // Deprecated
|
||||
removeListener: vitest.fn(), // Deprecated
|
||||
addEventListener: vitest.fn(),
|
||||
removeEventListener: vitest.fn(),
|
||||
dispatchEvent: vitest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
// From https://github.com/mantinedev/mantine/blob/master/configuration/jest/jsdom.mocks.js
|
||||
class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
window.ResizeObserver = ResizeObserver;
|
||||
|
||||
window.scrollTo = () => {};
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({ onUnhandledRequest: "error" });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
server.resetHandlers();
|
||||
server.use(
|
||||
http.get("/api/system/settings", () => {
|
||||
return HttpResponse.json({
|
||||
general: {
|
||||
theme: "auto",
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
afterAll(() => server.close());
|
|
@ -6,6 +6,10 @@ import { isProdEnv } from ".";
|
|||
type LoggerType = "info" | "warning" | "error";
|
||||
|
||||
export function LOG(type: LoggerType, msg: string, ...payload: any[]) {
|
||||
if (import.meta.env.MODE === "test") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isProdEnv) {
|
||||
let logger = console.log;
|
||||
if (type === "warning") {
|
||||
|
|
|
@ -133,7 +133,7 @@ export default defineConfig(({ mode, command }) => {
|
|||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "./src/tests/setup.ts",
|
||||
setupFiles: "./src/tests/setup.tsx",
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue