Translation swap added
This commit is contained in:
@@ -8,6 +8,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
it("switches page correctly on inputs change", () => {
|
it("switches page correctly on inputs change", () => {
|
||||||
cy.findByRole("textbox", { name: /translation query/i })
|
cy.findByRole("textbox", { name: /translation query/i })
|
||||||
|
.as("query")
|
||||||
.type("palabra");
|
.type("palabra");
|
||||||
cy.findByRole("textbox", { name: /translation result/i })
|
cy.findByRole("textbox", { name: /translation result/i })
|
||||||
.as("translation")
|
.as("translation")
|
||||||
@@ -15,27 +16,46 @@ it("switches page correctly on inputs change", () => {
|
|||||||
.url()
|
.url()
|
||||||
.should("include", "/auto/en/palabra");
|
.should("include", "/auto/en/palabra");
|
||||||
cy.findByRole("combobox", { name: /source language/i })
|
cy.findByRole("combobox", { name: /source language/i })
|
||||||
|
.as("source")
|
||||||
.select("es")
|
.select("es")
|
||||||
.url()
|
.url()
|
||||||
.should("include", "/es/en/palabra");
|
.should("include", "/es/en/palabra");
|
||||||
cy.findByRole("combobox", { name: /target language/i })
|
cy.findByRole("combobox", { name: /target language/i })
|
||||||
.select("ca");
|
.as("target")
|
||||||
cy.get("@translation")
|
.select("ca")
|
||||||
|
.get("@translation")
|
||||||
.should("have.value", "paraula")
|
.should("have.value", "paraula")
|
||||||
.url()
|
.url()
|
||||||
.should("include", "/es/ca/palabra");
|
.should("include", "/es/ca/palabra");
|
||||||
|
cy.findByRole("button", { name: /switch languages/i })
|
||||||
|
.click()
|
||||||
|
.get("@source")
|
||||||
|
.should("have.value", "ca")
|
||||||
|
.get("@target")
|
||||||
|
.should("have.value", "es")
|
||||||
|
.get("@query")
|
||||||
|
.should("have.value", "paraula")
|
||||||
|
.get("@translation")
|
||||||
|
.should("have.value", "palabra")
|
||||||
|
.url()
|
||||||
|
.should("include", "/ca/es/paraula");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switches first loaded page on language change", () => {
|
it("switches first loaded page and back and forth on language change", () => {
|
||||||
const query = faker.random.words();
|
const query = faker.random.words();
|
||||||
cy.visit(`/auto/en/${query}`);
|
cy.visit(`/auto/en/${query}`);
|
||||||
|
|
||||||
cy.findByRole("textbox", { name: /translation query/i })
|
cy.findByRole("textbox", { name: /translation query/i })
|
||||||
.should("have.value", query);
|
.should("have.value", query);
|
||||||
cy.findByRole("combobox", { name: /source language/i })
|
cy.findByRole("combobox", { name: /source language/i })
|
||||||
|
.as("source")
|
||||||
.select("eo")
|
.select("eo")
|
||||||
.url()
|
.url()
|
||||||
.should("include", `/eo/en/${encodeURIComponent(query)}`);
|
.should("include", `/eo/en/${encodeURIComponent(query)}`)
|
||||||
|
.get("@source")
|
||||||
|
.select("auto")
|
||||||
|
.url()
|
||||||
|
.should("include", `/auto/en/${encodeURIComponent(query)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't switch initial page on language change", () => {
|
it("doesn't switch initial page on language change", () => {
|
||||||
@@ -84,7 +104,7 @@ it("skips to main on 'skip link' click", () => {
|
|||||||
.should("include", "#main");
|
.should("include", "#main");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows error on >4 params", () => {
|
it("shows error on >=4 params", () => {
|
||||||
cy.visit(`/auto/en/translation/other`)
|
cy.visit("/auto/en/translation/other");
|
||||||
.findByText(404);
|
cy.findByText(404);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import { googleScrape, extractSlug } from "../utils/translate";
|
|||||||
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, statusCode, errorMsg, initial }) => {
|
const Page: FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ home, translationRes, statusCode, errorMsg, initial }) => {
|
||||||
const [{ source, target, query }, dispatch] = useReducer(langReducer, initialState);
|
const [{ source, target, query, delayedQuery, translation }, dispatch] = useReducer(langReducer, initialState);
|
||||||
const [delayedQuery, setDelayedQuery] = useState(initialState.query);
|
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLSelectElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -24,23 +23,27 @@ const Page: FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ translation,
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initial && dispatch({ type: Actions.SET_ALL, payload: { state: initial }});
|
const state = { ...initial, delayedQuery: initial?.query, translation: translationRes };
|
||||||
}, [initial]);
|
initial && dispatch({ type: Actions.SET_ALL, payload: { state }});
|
||||||
|
}, [initial, translationRes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = setTimeout(() => setDelayedQuery(query), 1000);
|
const timeout = setTimeout(() =>
|
||||||
|
dispatch({ type: Actions.SET_FIELD, payload: { key: "delayedQuery", value: query }}
|
||||||
|
), 1000);
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const queryIsEmpty = !delayedQuery || delayedQuery === initialState.query;
|
if (!delayedQuery || delayedQuery === initialState.query)
|
||||||
const queryIsInitial = delayedQuery === initial?.query;
|
return;
|
||||||
const sourceIsInitial = source === initialState.source || source === initial?.source;
|
if (!home && !initial)
|
||||||
const targetIsInitial = target === initialState.target || target === initial?.target;
|
return;
|
||||||
const allAreInitials = queryIsInitial && sourceIsInitial && targetIsInitial;
|
if (!home && delayedQuery === initial.query && source === initial.source && target === initial.target)
|
||||||
|
return;
|
||||||
|
|
||||||
queryIsEmpty || allAreInitials || Router.push(`/${source}/${target}/${encodeURIComponent(delayedQuery)}`);
|
Router.push(`/${source}/${target}/${encodeURIComponent(delayedQuery)}`);
|
||||||
}, [source, target, delayedQuery, initial]);
|
}, [source, target, delayedQuery, initial, home]);
|
||||||
|
|
||||||
const { sourceLangs, targetLangs } = retrieveFiltered(source, target);
|
const { sourceLangs, targetLangs } = retrieveFiltered(source, target);
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ export const getStaticPaths: GetStaticPaths = async () => ({
|
|||||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||||
if (!params?.slug || !Array.isArray(params.slug))
|
if (!params?.slug || !Array.isArray(params.slug))
|
||||||
return {
|
return {
|
||||||
props: {}
|
props: { home: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
const { source, target, query } = extractSlug(params.slug);
|
const { source, target, query } = extractSlug(params.slug);
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ describe("getStaticProps", () => {
|
|||||||
const target = faker.random.locale();
|
const target = faker.random.locale();
|
||||||
const query = faker.random.words();
|
const query = faker.random.words();
|
||||||
|
|
||||||
it("returns empty props on empty params", async () => {
|
it("returns home on empty params", async () => {
|
||||||
expect(await getStaticProps({ params: {} })).toStrictEqual({ props: {} });
|
expect(await getStaticProps({ params: {} })).toStrictEqual({ props: { home: true } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns not found on >4 params", async () => {
|
it("returns not found on >=4 params", async () => {
|
||||||
const slug = [source, target, query, ""];
|
const slug = [source, target, query, ""];
|
||||||
expect(await getStaticProps({ params: { slug } })).toStrictEqual({ notFound: true });
|
expect(await getStaticProps({ params: { slug } })).toStrictEqual({ notFound: true });
|
||||||
});
|
});
|
||||||
@@ -37,14 +37,13 @@ describe("getStaticProps", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns translation & initial values on 3 params", async () => {
|
it("returns translation & initial values on 3 params", async () => {
|
||||||
const translation = faker.random.words();
|
const translationRes = faker.random.words();
|
||||||
const html = htmlRes(translation);
|
resolveFetchWith(htmlRes(translationRes));
|
||||||
resolveFetchWith(html);
|
|
||||||
|
|
||||||
const slug = [source, target, query];
|
const slug = [source, target, query];
|
||||||
expect(await getStaticProps({ params: { slug } })).toStrictEqual({
|
expect(await getStaticProps({ params: { slug } })).toStrictEqual({
|
||||||
props: {
|
props: {
|
||||||
translation,
|
translationRes,
|
||||||
initial: {
|
initial: {
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
@@ -58,7 +57,7 @@ describe("getStaticProps", () => {
|
|||||||
|
|
||||||
describe("Page", () => {
|
describe("Page", () => {
|
||||||
it("loads the layout correctly", async () => {
|
it("loads the layout correctly", async () => {
|
||||||
render(<Page />);
|
render(<Page home={true} />);
|
||||||
|
|
||||||
expect(screen.getByRole("link", { name: /skip to content/i })).toBeEnabled();
|
expect(screen.getByRole("link", { name: /skip to content/i })).toBeEnabled();
|
||||||
expect(await screen.findByRole("img", { name: /logo/i })).toBeVisible();
|
expect(await screen.findByRole("img", { name: /logo/i })).toBeVisible();
|
||||||
@@ -68,7 +67,7 @@ describe("Page", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("switches the page on query change", async () => {
|
it("switches the page on query change", async () => {
|
||||||
render(<Page />)
|
render(<Page home={true} />)
|
||||||
|
|
||||||
const query = screen.getByRole("textbox", { name: /translation query/i });
|
const query = screen.getByRole("textbox", { name: /translation query/i });
|
||||||
userEvent.type(query, faker.random.words());
|
userEvent.type(query, faker.random.words());
|
||||||
@@ -89,8 +88,8 @@ describe("Page", () => {
|
|||||||
target: "es",
|
target: "es",
|
||||||
query: faker.random.words()
|
query: faker.random.words()
|
||||||
};
|
};
|
||||||
const translationText = faker.random.words();
|
const translationRes = faker.random.words();
|
||||||
render(<Page translation={translationText} initial={initial} />);
|
render(<Page translationRes={translationRes} initial={initial} />);
|
||||||
|
|
||||||
const source = screen.getByRole("combobox", { name: /source language/i });
|
const source = screen.getByRole("combobox", { name: /source language/i });
|
||||||
expect(source).toHaveValue(initial.source);
|
expect(source).toHaveValue(initial.source);
|
||||||
@@ -99,7 +98,7 @@ describe("Page", () => {
|
|||||||
const query = screen.getByRole("textbox", { name: /translation query/i });
|
const query = screen.getByRole("textbox", { name: /translation query/i });
|
||||||
expect(query).toHaveValue(initial.query);
|
expect(query).toHaveValue(initial.query);
|
||||||
const translation = screen.getByRole("textbox", { name: /translation result/i });
|
const translation = screen.getByRole("textbox", { name: /translation result/i });
|
||||||
expect(translation).toHaveValue(translationText);
|
expect(translation).toHaveValue(translationRes);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switches the page on language change", async () => {
|
it("switches the page on language change", async () => {
|
||||||
@@ -108,8 +107,8 @@ describe("Page", () => {
|
|||||||
target: "en",
|
target: "en",
|
||||||
query: faker.random.words()
|
query: faker.random.words()
|
||||||
};
|
};
|
||||||
const translationText = faker.random.words();
|
const translationRes = faker.random.words();
|
||||||
render(<Page translation={translationText} initial={initial} />);
|
render(<Page translationRes={translationRes} initial={initial} />);
|
||||||
|
|
||||||
const source = screen.getByRole("combobox", { name: /source language/i });
|
const source = screen.getByRole("combobox", { name: /source language/i });
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ describe("Page", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't switch the page on language change on the start page", async () => {
|
it("doesn't switch the page on language change on the start page", async () => {
|
||||||
render(<Page />);
|
render(<Page home={true} />);
|
||||||
|
|
||||||
const source = screen.getByRole("combobox", { name: /source language/i });
|
const source = screen.getByRole("combobox", { name: /source language/i });
|
||||||
|
|
||||||
@@ -132,6 +131,26 @@ describe("Page", () => {
|
|||||||
await waitFor(() => expect(Router.push).not.toHaveBeenCalled());
|
await waitFor(() => expect(Router.push).not.toHaveBeenCalled());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("switches languages & translations", async () => {
|
||||||
|
const initial = {
|
||||||
|
source: "es",
|
||||||
|
target: "ca",
|
||||||
|
query: faker.random.words()
|
||||||
|
};
|
||||||
|
const translationRes = faker.random.words();
|
||||||
|
render(<Page translationRes={translationRes} initial={initial} />);
|
||||||
|
|
||||||
|
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("");
|
||||||
|
|
||||||
|
await waitFor(() => expect(Router.push).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
it("renders error page on status code", async () => {
|
it("renders error page on status code", async () => {
|
||||||
const code = faker.random.number({ min: 400, max: 599 });
|
const code = faker.random.number({ min: 400, max: 599 });
|
||||||
render(<Page statusCode={code} />);
|
render(<Page statusCode={code} />);
|
||||||
|
|||||||
@@ -15,10 +15,13 @@ it("changes a field value", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("changes all fields", () => {
|
it("changes all fields", () => {
|
||||||
|
const query = faker.random.words();
|
||||||
const state = {
|
const state = {
|
||||||
source: faker.random.locale(),
|
source: faker.random.locale(),
|
||||||
target: faker.random.locale(),
|
target: faker.random.locale(),
|
||||||
query: faker.random.words()
|
query,
|
||||||
|
delayedQuery: query,
|
||||||
|
translation: faker.random.words()
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = langReducer(initialState, {
|
const res = langReducer(initialState, {
|
||||||
@@ -28,16 +31,23 @@ it("changes all fields", () => {
|
|||||||
expect(res).toStrictEqual(state);
|
expect(res).toStrictEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switches the languages", () => {
|
it("switches the languages & the translations", () => {
|
||||||
const state = {
|
const state = {
|
||||||
...initialState,
|
...initialState,
|
||||||
source: "es",
|
source: "es",
|
||||||
target: "ca"
|
target: "ca",
|
||||||
|
query: faker.random.words(),
|
||||||
|
translation: faker.random.words()
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = langReducer(state, { type: Actions.SWITCH_LANGS });
|
const res = langReducer(state, { type: Actions.SWITCH_LANGS });
|
||||||
expect(res.source).toStrictEqual(state.target);
|
expect(res).toStrictEqual({
|
||||||
expect(res.target).toStrictEqual(state.source);
|
source: state.target,
|
||||||
|
target: state.source,
|
||||||
|
query: state.translation,
|
||||||
|
delayedQuery: state.translation,
|
||||||
|
translation: ""
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resets the source while switching if they're the same", () => {
|
it("resets the source while switching if they're the same", () => {
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ describe("googleScrape", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("parses html response correctly", async () => {
|
it("parses html response correctly", async () => {
|
||||||
const translation = faker.random.words();
|
const translationRes = faker.random.words();
|
||||||
const html = htmlRes(translation);
|
const html = htmlRes(translationRes);
|
||||||
resolveFetchWith(html);
|
resolveFetchWith(html);
|
||||||
|
|
||||||
expect(await googleScrape(source, target, query)).toStrictEqual({ translation });
|
expect(await googleScrape(source, target, query)).toStrictEqual({ translationRes });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns status code on request error", async () => {
|
it("returns status code on request error", async () => {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { replaceBoth } from "./language";
|
|||||||
export const initialState = {
|
export const initialState = {
|
||||||
source: "auto",
|
source: "auto",
|
||||||
target: "en",
|
target: "en",
|
||||||
query: ""
|
query: "",
|
||||||
|
delayedQuery: "",
|
||||||
|
translation: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = typeof initialState;
|
type State = typeof initialState;
|
||||||
@@ -46,7 +48,10 @@ export default function reducer(state: State, action: Action) {
|
|||||||
source: source !== target
|
source: source !== target
|
||||||
? source
|
? source
|
||||||
: initialState.source,
|
: initialState.source,
|
||||||
target
|
target,
|
||||||
|
query: state.translation,
|
||||||
|
delayedQuery: state.translation,
|
||||||
|
translation: ""
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export async function googleScrape(
|
|||||||
target: string,
|
target: string,
|
||||||
query: string
|
query: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
translation?: string,
|
translationRes?: string,
|
||||||
statusCode?: number,
|
statusCode?: number,
|
||||||
errorMsg?: string
|
errorMsg?: string
|
||||||
}> {
|
}> {
|
||||||
@@ -26,11 +26,11 @@ export async function googleScrape(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const translation = cheerio.load(html)(".result-container").text().trim();
|
const translationRes = cheerio.load(html)(".result-container").text().trim();
|
||||||
|
|
||||||
return translation
|
return translationRes
|
||||||
? {
|
? {
|
||||||
translation
|
translationRes
|
||||||
} : {
|
} : {
|
||||||
errorMsg: "An error occurred while parsing the translation"
|
errorMsg: "An error occurred while parsing the translation"
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user