Tests for translate utils

This commit is contained in:
David
2021-03-12 16:18:26 +01:00
parent e1fee28805
commit 17dfdc3214
9 changed files with 144 additions and 18 deletions

View File

@@ -7,11 +7,11 @@ module.exports = {
'^.+\\.module\\.(css|sass|scss)$', '^.+\\.module\\.(css|sass|scss)$',
], ],
testMatch: [ testMatch: [
'<rootDir>/**/__tests__/**/*.{js,jsx,ts,tsx}', '<rootDir>/**/tests/*/**/*.{js,jsx,ts,tsx}',
'<rootDir>/**/*.{spec,test}.{js,jsx,ts,tsx}', '<rootDir>/**/*.{spec,test}.{js,jsx,ts,tsx}'
], ],
setupFilesAfterEnv: [ setupFilesAfterEnv: [
"<rootDir>/setupTests.ts" "<rootDir>/tests/setupTests.ts"
], ],
moduleFileExtensions: ["ts", "tsx", "js", "jsx"] moduleFileExtensions: ["ts", "tsx", "js", "jsx"]
} }

View File

@@ -19,6 +19,7 @@
"@testing-library/jest-dom": "^5.11.9", "@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.8.3", "@testing-library/user-event": "^12.8.3",
"@types/faker": "^5.1.7",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/node": "^14.14.33", "@types/node": "^14.14.33",
"@types/react": "^17.0.3", "@types/react": "^17.0.3",
@@ -34,7 +35,9 @@
"eslint-plugin-react": "^7.20.3", "eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.0.8", "eslint-plugin-react-hooks": "^4.0.8",
"eslint-plugin-testing-library": "^3.9.0", "eslint-plugin-testing-library": "^3.9.0",
"faker": "^5.4.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"jest-fetch-mock": "^3.0.3",
"typescript": "^4.2.3" "typescript": "^4.2.3"
}, },
"eslintConfig": { "eslintConfig": {
@@ -44,6 +47,8 @@
] ]
}, },
"babel": { "babel": {
"presets": ["next/babel"] "presets": [
"next/babel"
]
} }
} }

View File

@@ -8,7 +8,7 @@ import Languages from "../components/Languages";
import { retrieveFiltered } from "../utils/language"; import { retrieveFiltered } from "../utils/language";
import langReducer, { Actions, initialState } from "../utils/reducer"; import langReducer, { Actions, initialState } from "../utils/reducer";
const Page: FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ translation, error, initial }) => { const Page: FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ translation, statusCode, errorMsg, initial }) => {
const [{ source, target, query }, dispatch] = useReducer(langReducer, initialState); const [{ source, target, query }, dispatch] = useReducer(langReducer, initialState);
const [encodedQuery, setEncodedQuery] = useState(""); const [encodedQuery, setEncodedQuery] = useState("");
@@ -40,8 +40,8 @@ const Page: FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ translation,
const { sourceLangs, targetLangs } = retrieveFiltered(source, target); const { sourceLangs, targetLangs } = retrieveFiltered(source, target);
return error ? ( return statusCode ? (
<Error statusCode={error} /> <Error statusCode={statusCode} />
) : ( ) : (
<div> <div>
<Head> <Head>

View File

@@ -1 +0,0 @@
import '@testing-library/jest-dom';

View File

@@ -1,4 +1,5 @@
import { render, screen } from "@testing-library/react"; import { render, screen } from "@testing-library/react";
import faker from "faker";
import Error from "next/error"; import Error from "next/error";
it("renders a not found message on 404 code", () => { it("renders a not found message on 404 code", () => {
@@ -7,7 +8,7 @@ it("renders a not found message on 404 code", () => {
}); });
it("renders the correct status code", () => { it("renders the correct status code", () => {
const code = Math.floor(Math.random() * 500) + 100; const code = faker.random.number({ min: 100, max: 599 });
render(<Error statusCode={code} />); render(<Error statusCode={code} />);
expect(screen.getByText(code)).toBeVisible(); expect(screen.getByText(code)).toBeVisible();
}); });

3
tests/setupTests.ts Normal file
View File

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

View File

@@ -0,0 +1,75 @@
import faker from "faker";
import { googleScrape, extractSlug } from "../../utils/translate";
const source = faker.random.locale();
const target = faker.random.locale();
const query = faker.random.words();
describe("googleScrape", () => {
beforeEach(() => {
fetchMock.resetMocks();
});
it("parses html response correctly", async () => {
const translation = faker.random.words();
const className = "result-container";
const html = `
<div class=${className}>
${translation}
</div>
`;
fetchMock.mockResponseOnce(async () => ({ body: html }));
expect(await googleScrape(source, target, query)).toStrictEqual({ translation });
});
it("returns status code on request error", async () => {
const status = faker.random.number({ min: 400, max: 499 });
fetchMock.mockResponseOnce(async () => ({ status }));
expect(await googleScrape(source, target, query)).toStrictEqual({ statusCode: status });
});
it("returns correct message on network error", async () => {
fetchMock.mockRejectOnce();
const res = await googleScrape(source, target, query);
expect(res?.errorMsg).toMatch(/retrieving/);
});
it("returns correct message on parsing wrong class", async () => {
const translation = faker.random.words();
const className = "wrong-container";
const html = `
<div class=${className}>
${translation}
</div>
`;
fetchMock.mockResponseOnce(async () => ({ body: html }));
const res = await googleScrape(source, target, query);
expect(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.random.number({ min: 4, max: 50 });
const array = Array(length).fill("");
expect(extractSlug(array)).toStrictEqual({});
});
});

View File

@@ -7,20 +7,33 @@ export async function googleScrape(
query: string query: string
): Promise<{ ): Promise<{
translation?: string, translation?: string,
error?: number statusCode?: number,
errorMsg?: string
}> { }> {
const parsed = replaceBoth("mapping", { source, target }); const parsed = replaceBoth("mapping", { source, target });
const res = await fetch(`https://translate.google.com/m?sl=${parsed.source}&tl=${parsed.target}&q=${encodeURIComponent(query)}`); const res = await fetch(
`https://translate.google.com/m?sl=${parsed.source}&tl=${parsed.target}&q=${encodeURIComponent(query)}`
).catch(() => null);
if (!res)
return {
errorMsg: "An error occurred while retrieving the translation"
}
if (!res.ok) if (!res.ok)
return { return {
error: res.status statusCode: res.status
}; };
const html = await res.text(); const html = await res.text();
return { const translation = cheerio.load(html)(".result-container").text().trim();
translation: cheerio.load(html)(".result-container").text()
}; return translation
? {
translation
} : {
errorMsg: "An error occurred while parsing the translation"
};
} }
export function extractSlug(slug: string[]): { export function extractSlug(slug: string[]): {
@@ -33,10 +46,10 @@ export function extractSlug(slug: string[]): {
case 1: case 1:
return { query: p1 }; return { query: p1 };
case 2: case 2:
return { target: p1, query: p2 } return { target: p1, query: p2 };
case 3: case 3:
return { source: p1, target: p2, query: p3 } return { source: p1, target: p2, query: p3 };
default: default:
return {} return {};
} }
} }

View File

@@ -719,6 +719,11 @@
dependencies: dependencies:
"@babel/types" "^7.3.0" "@babel/types" "^7.3.0"
"@types/faker@^5.1.7":
version "5.1.7"
resolved "https://registry.yarnpkg.com/@types/faker/-/faker-5.1.7.tgz#1c3f0655f6e912f5578ce40baa941ab8705fc0ef"
integrity sha512-ByseCEyhb+64fapaFR/yASUfAU+Ia4LpCnHhoNMQJFSIBvbtFoEZQB0elwrSF/idpKL5OZvmZhCCukGV8Zi22w==
"@types/graceful-fs@^4.1.2": "@types/graceful-fs@^4.1.2":
version "4.1.5" version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -1800,6 +1805,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
cross-fetch@^3.0.4:
version "3.0.6"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c"
integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==
dependencies:
node-fetch "2.6.1"
cross-spawn@^6.0.0: cross-spawn@^6.0.0:
version "6.0.5" version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -2586,6 +2598,11 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
faker@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/faker/-/faker-5.4.0.tgz#f18e55993c6887918182b003d163df14daeb3011"
integrity sha512-Y9n/Ky/xZx/Bj8DePvXspUYRtHl/rGQytoIT5LaxmNwSe3wWyOeOXb3lT6Dpipq240PVpeFaGKzScz/5fvff2g==
fast-deep-equal@^3.1.1: fast-deep-equal@^3.1.1:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -3480,6 +3497,14 @@ jest-environment-node@^26.6.2:
jest-mock "^26.6.2" jest-mock "^26.6.2"
jest-util "^26.6.2" jest-util "^26.6.2"
jest-fetch-mock@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
dependencies:
cross-fetch "^3.0.4"
promise-polyfill "^8.1.3"
jest-get-type@^26.3.0: jest-get-type@^26.3.0:
version "26.3.0" version "26.3.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
@@ -4759,6 +4784,11 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-polyfill@^8.1.3:
version "8.2.0"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==
prompts@^2.0.1: prompts@^2.0.1:
version "2.4.0" version "2.4.0"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7"