Refactor to external scraper and update dependencies (#113)

This commit is contained in:
David
2022-06-15 23:37:15 +02:00
committed by GitHub
parent ff1ad202ae
commit 274e7f1a4b
49 changed files with 6952 additions and 4414 deletions

View File

@@ -1,9 +1,8 @@
import { render, screen } from "@tests/reactUtils";
import faker from "faker";
import { render, screen } from "./reactUtils";
import CustomError from "@components/CustomError";
const code = faker.datatype.number({ min: 400, max: 599 });
const text = faker.random.words();
const code = Math.random() * 199 + 400;
const text = "Testing fake error";
it("loads the layout correctly", async () => {
render(<CustomError statusCode={code} statusText={text} />);
@@ -16,7 +15,6 @@ it("loads the layout correctly", async () => {
});
it("renders the correct status code & text", () => {
const code = faker.datatype.number({ min: 400, max: 599 });
render(<CustomError statusCode={code} statusText={text} />);
expect(screen.getByText(code)).toBeVisible();

198
tests/Page.test.tsx Normal file
View File

@@ -0,0 +1,198 @@
import { render, screen, waitFor, act } from "./reactUtils";
import userEvent from "@testing-library/user-event";
import { localStorageSetMock } from "@mocks/localStorage";
import { routerMock } from "@mocks/next";
import {
fullInfoMock,
simpleInfoMock,
pronunciationInfoMock,
translationMock,
audioMock,
initialMock,
initialAutoMock
} from "@mocks/data";
import Page, { ResponseType } from "@pages/[[...slug]]";
beforeEach(() => {
routerMock.push.mockReset();
});
it("loads the layout correctly", async () => {
render(<Page type={ResponseType.HOME} />);
expect(screen.getByRole("link", { name: /skip to content/i })).toBeEnabled();
expect(await screen.findByRole("img", { name: /logo/i })).toBeVisible();
expect(screen.getByRole("button", { name: /toggle color mode/i })).toBeEnabled();
expect(screen.getByRole("link", { name: /github/i })).toBeEnabled();
expect(screen.getByText(/\xA9/)).toBeVisible();
});
it("switches the page on translate button click", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.HOME} />);
const query = screen.getByRole("textbox", { name: /translation query/i });
await waitFor(() => user.type(query, "Random query"));
const translate = screen.getByRole("button", { name: /translate/i });
await waitFor(() => user.click(translate));
expect(routerMock.push).toHaveBeenCalledTimes(1);
expect(screen.getByText(/loading translation/i)).toBeInTheDocument();
});
it("doesn't switch the page if nothing has changed", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.SUCCESS} initial={initialMock} translation={translationMock} info={simpleInfoMock} audio={audioMock} />);
const translate = screen.getByRole("button", { name: /translate/i });
await waitFor(() => user.click(translate));
expect(routerMock.push).not.toHaveBeenCalled();
expect(screen.queryByText(/loading translation/i)).not.toBeInTheDocument();
});
it("stores auto state in localStorage", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.HOME} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
await waitFor(() => user.click(switchAuto));
expect(localStorageSetMock).toHaveBeenLastCalledWith("isauto", "true");
await waitFor(() => user.click(switchAuto));
expect(localStorageSetMock).toHaveBeenLastCalledWith("isauto", "false");
});
it("switches the page on query change if auto is enabled", async () => {
jest.useFakeTimers();
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<Page type={ResponseType.HOME} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
await waitFor(() => user.click(switchAuto));
const query = screen.getByRole("textbox", { name: /translation query/i });
await waitFor(() => user.type(query, "Random query"));
await waitFor(() => expect(routerMock.push).not.toHaveBeenCalled());
expect(screen.queryByText(/loading translation/i)).not.toBeInTheDocument();
act(() => {
jest.advanceTimersByTime(1000);
});
await waitFor(() => expect(routerMock.push).toHaveBeenCalledTimes(1));
expect(screen.getByText(/loading translation/i)).toBeInTheDocument();
jest.useRealTimers();
});
it("switches the page on language change if auto is enabled", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.SUCCESS} translation={translationMock} initial={initialAutoMock} info={simpleInfoMock} audio={audioMock} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
await waitFor(() => user.click(switchAuto));
const source = screen.getByRole("combobox", { name: /source language/i });
const sourceVal = "eo";
await waitFor(() => user.selectOptions(source, sourceVal));
expect(source).toHaveValue(sourceVal);
await waitFor(() => expect(routerMock.push).toHaveBeenCalledTimes(1));
expect(localStorageSetMock).toHaveBeenCalledWith("source", sourceVal);
});
it("doesn't switch the page on language change on the start page", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.HOME} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
await waitFor(() => user.click(switchAuto));
const source = screen.getByRole("combobox", { name: /source language/i });
const sourceVal = "eo";
await waitFor(() => user.selectOptions(source, sourceVal));
expect(source).toHaveValue(sourceVal);
await waitFor(() => expect(routerMock.push).not.toHaveBeenCalled());
});
it("switches languages & translations", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.SUCCESS} translation={translationMock} initial={initialMock} info={fullInfoMock} audio={audioMock} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
await waitFor(() => user.click(switchAuto));
const btnSwitch = screen.getByRole("button", { name: /switch languages/i });
await waitFor(() => user.click(btnSwitch));
expect(screen.getByRole("combobox", { name: /source language/i })).toHaveValue(initialMock.target);
expect(screen.getByRole("combobox", { name: /target language/i })).toHaveValue(initialMock.source);
expect(screen.getByRole("textbox", { name: /translation query/i })).toHaveValue(translationMock);
expect(screen.getByRole("textbox", { name: /translation result/i })).toHaveValue(initialMock.query);
await waitFor(() => expect(routerMock.push).toHaveBeenCalledTimes(1));
expect(localStorageSetMock).toHaveBeenLastCalledWith("target", initialMock.source);
});
it("switches auto with detected language", async () => {
const user = userEvent.setup();
render(<Page type={ResponseType.SUCCESS} translation={translationMock} initial={initialAutoMock} info={fullInfoMock} audio={audioMock} />);
const btnSwitch = screen.getByRole("button", { name: /switch languages/i });
await waitFor(() => user.click(btnSwitch));
expect(screen.getByRole("combobox", { name: /source language/i })).toHaveValue(initialAutoMock.target);
expect(screen.getByRole("combobox", { name: /target language/i })).toHaveValue(fullInfoMock.detectedSource);
expect(screen.getByRole("textbox", { name: /translation query/i })).toHaveValue(translationMock);
expect(screen.getByRole("textbox", { name: /translation result/i })).toHaveValue(initialAutoMock.query);
});
it("corrently shows query & translation pronunciations", async () => {
render(<Page type={ResponseType.SUCCESS} translation={translationMock} initial={initialMock} info={pronunciationInfoMock} audio={audioMock} />);
expect(screen.getByRole("combobox", { name: /source language/i })).toHaveValue(initialMock.source);
expect(screen.getByRole("combobox", { name: /target language/i })).toHaveValue(initialMock.target);
expect(screen.getByRole("textbox", { name: /translation query/i })).toHaveValue(initialMock.query);
expect(screen.getByRole("textbox", { name: /translation result/i })).toHaveValue(translationMock);
expect(screen.getByText(pronunciationInfoMock.pronunciation.query!)).toBeVisible();
expect(screen.getByText(pronunciationInfoMock.pronunciation.translation!)).toBeVisible();
});
it("translates & loads initials correctly", async () => {
render(<Page type={ResponseType.SUCCESS} translation={translationMock} initial={initialMock} info={fullInfoMock} audio={audioMock} />);
const source = screen.getByRole("combobox", { name: /source language/i });
expect(source).toHaveValue(initialMock.source);
const target = screen.getByRole("combobox", { name: /target language/i });
expect(target).toHaveValue(initialMock.target);
const query = screen.getByRole("textbox", { name: /translation query/i });
expect(query).toHaveValue(initialMock.query);
const translation = screen.getByRole("textbox", { name: /translation result/i });
expect(translation).toHaveValue(translationMock);
});
it("loads audio & clipboard correctly", async () => {
render(<Page type={ResponseType.SUCCESS} translation={translationMock} initial={initialMock} info={simpleInfoMock} audio={audioMock} />);
const btnsAudio = screen.getAllByRole("button", { name: /play audio/i });
btnsAudio.forEach(btn => expect(btn).toBeVisible());
const btnCopy = screen.getByRole("button", { name: /copy to clipboard/i });
expect(btnCopy).toBeEnabled();
});
it("shows alert correctly on error", async () => {
const errorMsg = "Random error";
render(<Page type={ResponseType.ERROR} initial={initialMock} errorMsg={errorMsg} />);
const alert = screen.getByRole("alert");
await waitFor(() => expect(alert).toBeVisible());
expect(alert).toHaveTextContent(/unexpected error/i);
expect(alert).toHaveTextContent(errorMsg);
});

View File

@@ -1,13 +0,0 @@
import { MockResponseInit } from "jest-fetch-mock";
export const htmlRes = (translation: string, className = "result-container") => `
<div>
<div class=${className}>
${translation}
</div>
</div>
`;
export const resolveFetchWith = (params: string | MockResponseInit) => (
fetchMock.mockResponseOnce(async () => params)
);

View File

@@ -1,242 +0,0 @@
import { render, screen, waitFor } from "@tests/reactUtils";
import { htmlRes, resolveFetchWith } from "@tests/commonUtils";
import userEvent from "@testing-library/user-event";
import faker from "faker";
import Page, { getStaticProps } from "@pages/[[...slug]]";
import { localStorageSetMock } from "@mocks/localStorage";
import { routerPushMock } from "@mocks/next";
beforeEach(() => {
fetchMock.resetMocks();
routerPushMock.mockReset();
});
describe("getStaticProps", () => {
const source = "es";
const target = "ca";
const query = faker.random.words();
it("returns home on empty params", async () => {
expect(await getStaticProps({ params: {} })).toStrictEqual({ props: { home: true } });
});
it("returns not found on >=4 params", async () => {
const slug = [source, target, query, ""];
expect(await getStaticProps({ params: { slug } })).toStrictEqual({ notFound: true });
});
it("redirects on 1 param", async () => {
const slug = [query];
expect(await getStaticProps({ params: { slug } })).toMatchObject({ redirect: expect.any(Object) });
});
it("redirects on 2 params", async () => {
const slug = [target, query];
expect(await getStaticProps({ params: { slug } })).toMatchObject({ redirect: expect.any(Object) });
});
it("returns translation, audio & initial values on 3 params", async () => {
const translationRes = faker.random.words();
resolveFetchWith(htmlRes(translationRes));
const slug = [source, target, query];
expect(await getStaticProps({ params: { slug } })).toStrictEqual({
props: {
translationRes,
audio: {
source: expect.any(Array),
target: expect.any(Array)
},
initial: {
source,
target,
query
}
},
revalidate: expect.any(Number)
});
});
});
describe("Page", () => {
const translationRes = faker.random.words();
const randomAudio = Array.from({ length: 10 }, () => faker.datatype.number(100));
const audio = {
source: randomAudio,
target: randomAudio
};
it("loads the layout correctly", async () => {
render(<Page home={true} />);
expect(screen.getByRole("link", { name: /skip to content/i })).toBeEnabled();
expect(await screen.findByRole("img", { name: /logo/i })).toBeVisible();
expect(screen.getByRole("button", { name: /toggle color mode/i })).toBeEnabled();
expect(screen.getByRole("link", { name: /github/i })).toBeEnabled();
expect(screen.getByText(/\xA9/)).toBeVisible();
});
it("switches the page on translate button click", async () => {
render(<Page home={true} />);
const query = screen.getByRole("textbox", { name: /translation query/i });
userEvent.type(query, faker.random.words());
const translate = screen.getByRole("button", { name: /translate/i });
translate.click();
expect(routerPushMock).toHaveBeenCalledTimes(1);
expect(screen.getByText(/loading translation/i)).toBeInTheDocument();
});
it("doesn't switch the page if nothing has changed", async () => {
const initial = {
source: "ca",
target: "es",
query: faker.random.words()
};
render(<Page translationRes={translationRes} audio={audio} initial={initial} />);
const translate = screen.getByRole("button", { name: /translate/i });
translate.click();
expect(routerPushMock).not.toHaveBeenCalled();
expect(screen.queryByText(/loading translation/i)).not.toBeInTheDocument();
});
it("stores auto state in localStorage", async () => {
render(<Page home={true} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
switchAuto.click();
await waitFor(() => expect(localStorageSetMock).toHaveBeenLastCalledWith("isauto", "true"));
switchAuto.click();
await waitFor(() => expect(localStorageSetMock).toHaveBeenLastCalledWith("isauto", "false"));
});
it("switches the page on query change if auto is enabled", async () => {
render(<Page home={true} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
switchAuto.click();
const query = screen.getByRole("textbox", { name: /translation query/i });
userEvent.type(query, faker.random.words());
await waitFor(
() => {
expect(routerPushMock).not.toHaveBeenCalled();
expect(screen.queryByText(/loading translation/i)).not.toBeInTheDocument();
},
{ timeout: 250 }
);
await waitFor(
() => {
expect(routerPushMock).toHaveBeenCalledTimes(1);
expect(screen.getByText(/loading translation/i)).toBeInTheDocument();
},
{ timeout: 2500 }
);
});
it("switches the page on language change if auto is enabled", async () => {
const initial = {
source: "auto",
target: "en",
query: faker.random.words()
};
render(<Page translationRes={translationRes} audio={audio} initial={initial} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
switchAuto.click();
const source = screen.getByRole("combobox", { name: /source language/i });
const sourceVal = "eo";
userEvent.selectOptions(source, sourceVal);
expect(source).toHaveValue(sourceVal);
await waitFor(() => expect(routerPushMock).toHaveBeenCalledTimes(1));
expect(localStorageSetMock).toHaveBeenCalledWith("source", sourceVal);
});
it("doesn't switch the page on language change on the start page", async () => {
render(<Page home={true} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
switchAuto.click();
const source = screen.getByRole("combobox", { name: /source language/i });
const sourceVal = "eo";
userEvent.selectOptions(source, sourceVal);
expect(source).toHaveValue(sourceVal);
await waitFor(() => expect(routerPushMock).not.toHaveBeenCalled());
});
it("switches languages & translations", async () => {
const initial = {
source: "es",
target: "ca",
query: faker.random.words()
};
render(<Page translationRes={translationRes} audio={audio} initial={initial} />);
const switchAuto = screen.getByRole("button", { name: /switch auto/i });
switchAuto.click();
const btnSwitch = screen.getByRole("button", { name: /switch languages/i });
userEvent.click(btnSwitch);
expect(screen.getByRole("combobox", { name: /source language/i })).toHaveValue(initial.target);
expect(screen.getByRole("combobox", { name: /target language/i })).toHaveValue(initial.source);
expect(screen.getByRole("textbox", { name: /translation query/i })).toHaveValue(translationRes);
expect(screen.getByRole("textbox", { name: /translation result/i })).toHaveValue(initial.query);
await waitFor(() => expect(routerPushMock).toHaveBeenCalledTimes(1));
expect(localStorageSetMock).toHaveBeenLastCalledWith("target", initial.source);
});
it("translates & loads initials correctly", async () => {
const initial = {
source: "ca",
target: "es",
query: faker.random.words()
};
render(<Page translationRes={translationRes} audio={audio} initial={initial} />);
const source = screen.getByRole("combobox", { name: /source language/i });
expect(source).toHaveValue(initial.source);
const target = screen.getByRole("combobox", { name: /target language/i });
expect(target).toHaveValue(initial.target);
const query = screen.getByRole("textbox", { name: /translation query/i });
expect(query).toHaveValue(initial.query);
const translation = screen.getByRole("textbox", { name: /translation result/i });
expect(translation).toHaveValue(translationRes);
});
it("loads audio & clipboard correctly", async () => {
const initial = {
source: "eo",
target: "zh",
query: faker.random.words()
};
render(<Page translationRes={translationRes} audio={audio} initial={initial} />);
const btnsAudio = screen.getAllByRole("button", { name: /play audio/i });
btnsAudio.forEach(btn => expect(btn).toBeVisible());
const btnCopy = screen.getByRole("button", { name: /copy to clipboard/i });
expect(btnCopy).toBeEnabled();
});
it("shows alert correctly on error", async () => {
const errorMsg = faker.random.words();
render(<Page errorMsg={errorMsg} />);
const alert = screen.getByRole("alert");
await waitFor(() => expect(alert).toBeVisible());
expect(alert).toHaveTextContent(/unexpected error/i);
expect(alert).toHaveTextContent(errorMsg);
});
});

View File

@@ -1,254 +0,0 @@
import { createTestClient } from "apollo-server-testing";
import { ApolloServer } from "apollo-server-micro";
import faker from "faker";
import { htmlRes, resolveFetchWith } from "@tests/commonUtils";
import { typeDefs, resolvers } from "@pages/api/graphql";
beforeEach(() => {
fetchMock.resetMocks();
});
const { query } = createTestClient(new ApolloServer({ typeDefs, resolvers }));
it("doesn't trigger fetch if neither target nor audio are specified", async () => {
const text = faker.random.words();
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
source {
text
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({ translation: { source: { text } } });
expect(fetch).not.toHaveBeenCalled();
});
it("returns translation triggering fetch", async () => {
const text = faker.random.words();
const translation = faker.random.words();
resolveFetchWith(htmlRes(translation));
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
target {
text
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({ translation: { target: { text: translation } } });
expect(fetch).toHaveBeenCalledTimes(1);
});
it("returns audio triggering fetch", async () => {
const lang = "es";
const text = faker.random.words();
resolveFetchWith({ status: 200 });
const { data } = await query({
query: `
query($lang: String! $text: String!) {
audio(lang: $lang query: $text) {
lang {
code
}
text
audio
}
}
`,
variables: { lang, text }
});
expect(data).toMatchObject({ audio: { lang: { code: lang }, text, audio: expect.any(Array) } });
expect(fetch).toHaveBeenCalledTimes(1);
});
it("returns null and throws on translation error", async () => {
const text = faker.random.words();
fetchMock.mockRejectOnce();
const { data, errors } = await query({
query: `
query($text: String!) {
translation(query: $text) {
target {
text
}
}
}
`,
variables: { text }
});
expect(data).toBeNull();
expect(errors).toBeTruthy();
});
it("returns null and throws on audio error", async () => {
const lang = "es";
const text = faker.random.words();
fetchMock.mockRejectOnce();
const { data, errors } = await query({
query: `
query($lang: String! $text: String!) {
audio(lang: $lang query: $text) {
audio
}
}
`,
variables: { lang, text }
});
expect(data).toBeNull();
expect(errors).toBeTruthy();
});
it("keeps a default value for both source and target languages", async () => {
const text = faker.random.words();
const translation = faker.random.words();
resolveFetchWith(htmlRes(translation));
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
source {
lang {
code
name
}
}
target {
lang {
code
name
}
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({
translation: {
source: {
lang: {
code: "auto",
name: "Detect"
}
},
target: {
lang: {
code: "en",
name: "English"
}
}
}
});
});
it("throws error on empty query in translation", async () => {
const { errors } = await query({
query: `
query {
translation {
source {
lang {
code
}
}
target {
lang {
code
}
}
}
}
`
});
expect(errors).toBeTruthy();
});
it("throws error on empty lang or query in audio", async () => {
const lang = "es";
const text = faker.random.words();
const { errors: queryErrors } = await query({
query: `
query($lang: String!) {
audio(lang: $lang) {
lang {
code
}
text
}
}
`,
variables: { lang }
});
expect(queryErrors).toBeTruthy();
const { errors: langErrors } = await query({
query: `
query($text: String!) {
audio(query: $text) {
lang {
code
}
text
}
}
`,
variables: { text }
});
expect(langErrors).toBeTruthy();
});
it("returns languages on empty type", async () => {
const { data } = await query({
query: `
query {
languages {
code
}
}
`
});
expect(data).toMatchObject({ languages: expect.any(Array) });
});
it("returns languages on 'source' type", async () => {
const { data } = await query({
query: `
query($type: LangType!) {
languages(type: $type) {
code
}
}
`,
variables: { type: "SOURCE" }
});
expect(data).toMatchObject({ languages: expect.any(Array) });
});
it("returns languages on 'target' type", async () => {
const { data } = await query({
query: `
query($type: LangType!) {
languages(type: $type) {
code
}
}
`,
variables: { type: "TARGET" }
});
expect(data).toMatchObject({ languages: expect.any(Array) });
});

View File

@@ -1,95 +0,0 @@
import httpMocks from "node-mocks-http";
import faker from "faker";
import { htmlRes, resolveFetchWith } from "@tests/commonUtils";
import handler from "@pages/api/v1/[[...slug]]";
beforeEach(() => {
fetchMock.resetMocks();
});
const source = "es";
const target = "ca";
const query = faker.random.words();
const slug = [source, target, query];
it("returns 404 on <3 params", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: [source, target] }
});
await handler(req, res);
expect(res.statusCode).toBe(404);
});
it("returns 404 on >3 params", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: [source, target, query, ""] }
});
await handler(req, res);
expect(res.statusCode).toBe(404);
});
it("returns 405 on forbidden method", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "POST",
query: { slug }
});
await handler(req, res);
expect(res.statusCode).toBe(405);
});
it("returns translation on scrapping resolve", async () => {
const translationRes = faker.random.words();
resolveFetchWith(htmlRes(translationRes));
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug }
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ translation: translationRes });
});
it("returns 500 on scrapping error", async () => {
fetchMock.mockRejectOnce();
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug }
});
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res._getJSONData()).toStrictEqual({ error: expect.any(String) });
});
it("returns audio on audio request", async () => {
resolveFetchWith({ status: 200 });
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["audio", target, query] }
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ audio: expect.any(Array) });
});
it("returns 500 on audio request error", async () => {
fetchMock.mockRejectOnce();
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["audio", target, query] }
});
await handler(req, res);
expect(res.statusCode).toBe(500);
});

View File

@@ -1,65 +0,0 @@
import httpMocks from "node-mocks-http";
import handler from "@pages/api/v1/languages/[[...slug]]";
it("returns 404 on >1 params", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["one", "two"] }
});
await handler(req, res);
expect(res.statusCode).toBe(404);
});
it("returns 405 on forbidden method", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "POST",
query: {}
});
await handler(req, res);
expect(res.statusCode).toBe(405);
});
it("returns 400 on wrong param", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["other"] }
});
await handler(req, res);
expect(res.statusCode).toBe(400);
});
it("returns 200 on empty param", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: {}
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ languages: expect.any(Array) });
});
it("returns 200 on 'source' param", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["source"] }
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ languages: expect.any(Array) });
});
it("returns 200 on 'target' param", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["target"] }
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ languages: expect.any(Array) });
});

View File

@@ -1,8 +1,9 @@
import { FC, ReactElement } from "react";
import { FC, ReactElement, PropsWithChildren } from "react";
import { render, RenderOptions } from "@testing-library/react";
import { ChakraProvider } from "@chakra-ui/react";
import theme from "@theme";
import { Layout } from "@components";
import { RouterProviderMock } from "@mocks/next";
// Jest JSDOM bug
Object.defineProperty(window, 'matchMedia', {
@@ -19,12 +20,14 @@ Object.defineProperty(window, 'matchMedia', {
})),
});
const Providers: FC = ({ children }) => (
<ChakraProvider theme={theme}>
<Layout>
{children}
</Layout>
</ChakraProvider>
const Providers: FC<PropsWithChildren> = ({ children }) => (
<RouterProviderMock>
<ChakraProvider theme={theme}>
<Layout>
{children}
</Layout>
</ChakraProvider>
</RouterProviderMock>
);
const customRender = (

View File

@@ -1,3 +1 @@
import '@testing-library/jest-dom';
import jestFetchMock from 'jest-fetch-mock';
jestFetchMock.enableMocks();

View File

@@ -1,68 +0,0 @@
import faker from "faker";
import { replaceBoth, retrieveFromType, getName, CheckType, LangType } from "@utils/language";
import { languages, exceptions, mappings } from "@utils/languages.json";
describe("replaceBoth", () => {
const testReplacer = (
checkType: CheckType,
checkObj: {
[key in LangType]: {
[key: string]: string
}
},
langType: LangType
) => (
Object.entries(checkObj[langType]).forEach(([code, replacement]) => {
const res = replaceBoth(checkType, { source: "auto", target: "en", [langType]: code })
expect(res[langType]).toBe(replacement);
})
);
it("replaces excepted sources correctly", () => {
testReplacer("exception", exceptions, "source");
});
it("replaces excepted targets correctly", () => {
testReplacer("exception", exceptions, "target");
});
it("replaces mapped sources correctly", () => {
testReplacer("mapping", mappings, "source");
});
it("replaces mapped targets correctly", () => {
testReplacer("mapping", mappings, "target");
});
});
describe("retrieveFromType", () => {
const checkExceptions = (langType: LangType) => (
retrieveFromType(langType).forEach(([code]) => !Object.keys(exceptions).includes(code))
);
it("returns full list on empty type", () => {
expect(retrieveFromType()).toStrictEqual(Object.entries(languages));
});
it("filters source exceptions", () => {
checkExceptions("source");
});
it("filters target exceptions", () => {
checkExceptions("target");
});
});
describe("getName", () => {
it("returns name from valid code", () => {
const langEntries = Object.entries(languages);
const randomEntry = faker.random.arrayElement(langEntries);
const [code, name] = randomEntry;
expect(getName(code)).toEqual(name);
});
it("returns null on wrong code", () => {
const randomCode = faker.random.words();
expect(getName(randomCode)).toBeNull();
});
});

View File

@@ -1,83 +0,0 @@
import faker from "faker";
import langReducer, { Actions, initialState } from "@utils/reducer";
it("changes a field value", () => {
const query = faker.random.words();
const res = langReducer(initialState, {
type: Actions.SET_FIELD,
payload: {
key: "query",
value: query
}
});
expect(res).toStrictEqual({ ...initialState, query });
});
it("changes all fields", () => {
const query = faker.random.words();
const state = {
source: "zh",
target: "zh_HANT",
query,
delayedQuery: query,
translation: faker.random.words(),
isLoading: faker.datatype.boolean()
} as const;
const res = langReducer(initialState, {
type: Actions.SET_ALL,
payload: { state }
});
expect(res).toStrictEqual(state);
});
it("switches target on source change", () => {
const state = {
...initialState,
source: "es",
target: "ca"
} as const;
const res = langReducer(state, {
type: Actions.SET_FIELD,
payload: {
key: "source",
value: state.target
}
});
expect(res.source).toStrictEqual(state.target);
expect(res.target).toStrictEqual(state.source);
});
it("switches the languages & the translations", () => {
const state = {
...initialState,
source: "es",
target: "ca",
query: faker.random.words(),
translation: faker.random.words()
} as const;
const res = langReducer(state, { type: Actions.SWITCH_LANGS });
expect(res).toStrictEqual({
source: state.target,
target: state.source,
query: state.translation,
delayedQuery: state.translation,
translation: state.query,
isLoading: initialState.isLoading
});
});
it("resets the source while switching if they're the same", () => {
const state = {
...initialState,
source: "eo",
target: "eo"
} as const;
const res = langReducer(state, { type: Actions.SWITCH_LANGS });
expect(res.source).toStrictEqual(initialState.source);
expect(res.target).toStrictEqual(state.source);
});

View File

@@ -1,86 +0,0 @@
import { htmlRes, resolveFetchWith } from "@tests/commonUtils";
import faker from "faker";
import { googleScrape, extractSlug, textToSpeechScrape } from "@utils/translate";
const source = "es";
const target = "ca";
const query = faker.random.words();
describe("googleScrape", () => {
beforeEach(() => {
fetchMock.resetMocks();
});
it("parses html response correctly", async () => {
const translationRes = faker.random.words();
const html = htmlRes(translationRes);
resolveFetchWith(html);
expect(await googleScrape(source, target, query)).toStrictEqual({ translationRes });
});
it("returns correct message on request error", async () => {
const status = faker.datatype.number({ min: 400, max: 499 });
resolveFetchWith({ status });
const res = await googleScrape(source, target, query);
expect("errorMsg" in res && res.errorMsg).toMatch(/retrieving/);
});
it("returns correct message on network error", async () => {
fetchMock.mockRejectOnce();
const res = await googleScrape(source, target, query);
expect("errorMsg" in res && res.errorMsg).toMatch(/retrieving/);
});
it("returns correct message on parsing wrong class", async () => {
const translation = faker.random.words();
const className = "wrong-container";
const html = htmlRes(translation, className);
resolveFetchWith(html);
const res = await googleScrape(source, target, query);
expect("errorMsg" in res && res.errorMsg).toMatch(/parsing/);
});
});
describe("extractSlug", () => {
it("returns 'query' for 1 param", () => {
expect(extractSlug([query])).toStrictEqual({ query });
});
it("returns 'target' & 'query' resp. for 2 params", () => {
expect(extractSlug([target, query])).toStrictEqual({ target, query });
});
it("returns 'source', 'target' & 'query' resp. for 3 param", () => {
expect(extractSlug([source, target, query])).toStrictEqual({ source, target, query });
});
it("returns empty object on 0 or >4 params", () => {
expect(extractSlug([])).toStrictEqual({});
const length = faker.datatype.number({ min: 4, max: 50 });
const array = Array(length).fill("");
expect(extractSlug(array)).toStrictEqual({});
});
});
describe("textToSpeechScrape", () => {
it("returns an array on successful request", async () => {
resolveFetchWith({ status: 200 });
expect(await textToSpeechScrape(target, query)).toEqual(expect.any(Array));
});
it("returns 'null' on request error", async () => {
const status = faker.datatype.number({ min: 400, max: 499 });
resolveFetchWith({ status });
expect(await textToSpeechScrape(target, query)).toBeNull();
});
it("returns 'null' on network error", async () => {
fetchMock.mockRejectOnce();
expect(await textToSpeechScrape(target, query)).toBeNull();
});
});