Skip to content

Commit b631ca2

Browse files
committed
Added initial tests setup along with some refactors
1 parent 5f6b877 commit b631ca2

File tree

14 files changed

+2824
-103
lines changed

14 files changed

+2824
-103
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["next/babel"]
3+
}

i18n/helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { localesMessages, Locale, Translate, LocaleKey } from './locales';
2+
3+
export function getTranslate(locale: Locale): Translate {
4+
return function translate(key: LocaleKey) {
5+
return localesMessages[locale]?.[key] || '';
6+
};
7+
}

i18n/hooks.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useRouter } from 'next/router';
2+
3+
import { Locale, Translate } from './locales';
4+
import { getTranslate } from './helpers';
5+
6+
type I18n = {
7+
t: Translate;
8+
locale: Locale;
9+
};
10+
11+
export function useI18n(): I18n {
12+
const router = useRouter();
13+
14+
const locale = router.locale as Locale;
15+
16+
return {
17+
locale,
18+
t: getTranslate(locale),
19+
};
20+
}

i18n/index.ts

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,3 @@
1-
const common = {
2-
author: 'Luís Takahashi',
3-
};
4-
5-
const portuguese = {
6-
...common,
7-
welcome: 'Olá 👋, eu sou Luis Takahashi',
8-
bio:
9-
'Um entusiasta apaixonado por Typecript e engenheiro de software frontend do Brasil',
10-
footer: 'Todos os direitos reservados',
11-
backtohome: 'Ir a página inicial 🏡',
12-
404: 'Ops esta página não foi encontrada.',
13-
};
14-
15-
const english = {
16-
...common,
17-
welcome: "Hi 👋, I'm Luis Takahashi",
18-
bio:
19-
'A passionate Typescript enthusiast and frontend software engineer from Brazil',
20-
footer: 'All rights reserved',
21-
backtohome: 'Go to home page 🏡',
22-
404: 'Ops this page could not be found.',
23-
};
24-
25-
const locales = {
26-
'pt-BR': portuguese,
27-
'en-US': english,
28-
};
29-
30-
type Locales = typeof locales;
31-
export type Locale = keyof Locales;
32-
type LocaleKey = keyof typeof portuguese | keyof typeof english;
33-
export type Translate = (key: LocaleKey) => string;
34-
35-
export function getTranslate(locale: Locale): Translate {
36-
return function translate(key: LocaleKey) {
37-
return locales[locale]?.[key] || '';
38-
};
39-
}
1+
export * from './hooks';
2+
export * from './helpers';
3+
export * from './locales';

i18n/locales.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const common = {
2+
author: 'Luís Takahashi',
3+
} as const;
4+
5+
const portuguese = {
6+
...common,
7+
welcome: 'Olá 👋, eu sou Luís Takahashi',
8+
authorImageAlt: 'Ilustração do Luís Takahashi em desenho',
9+
bio:
10+
'Um entusiasta apaixonado por Typecript e engenheiro de software frontend do Brasil',
11+
footer: 'Todos os direitos reservados',
12+
backtohome: 'Ir a página inicial 🏡',
13+
404: 'Ops esta página não foi encontrada.',
14+
} as const;
15+
16+
const english = {
17+
...common,
18+
welcome: "Hi 👋, I'm Luís Takahashi",
19+
authorImageAlt: 'Luís Takahashi illustration in drawing',
20+
bio:
21+
'A passionate Typescript enthusiast and frontend software engineer from Brazil',
22+
footer: 'All rights reserved',
23+
backtohome: 'Go to home page 🏡',
24+
404: 'Ops this page could not be found.',
25+
} as const;
26+
27+
export enum Locale {
28+
ptBR = 'pt-BR',
29+
enUS = 'en-US',
30+
}
31+
32+
export const localesMessages = {
33+
[Locale.ptBR]: portuguese,
34+
[Locale.enUS]: english,
35+
} as const;
36+
37+
export const locales: Locale[] = Object.values(Locale);
38+
39+
export type LocalesMessages = typeof localesMessages;
40+
41+
export type LocaleKey = keyof typeof portuguese | keyof typeof english;
42+
43+
export type Translate = (key: LocaleKey) => string;

jest.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
3+
testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
4+
modulePaths: ['<rootDir>']
5+
};

jest.setup.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import '@testing-library/jest-dom';
2+
3+
import { images } from './next.config';
4+
5+
process.env = {
6+
...process.env,
7+
__NEXT_I18N_SUPPORT: 'true',
8+
__NEXT_IMAGE_OPTS: (images as unknown) as string,
9+
};

next.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ module.exports = {
66
images: {
77
deviceSizes: [320, 420, 768, 1024, 1200],
88
iconSizes: [],
9+
imageSizes: [],
910
domains: ['takah.dev', 'luis-takahashi.dev'],
1011
path: '/_next/image',
1112
loader: 'default',
1213
},
14+
webpack: (config, { webpack: { IgnorePlugin } }) => {
15+
config.plugins.push(new IgnorePlugin(/\/__tests__\//));
16+
return config;
17+
},
1318
};

package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"build": "next build",
1717
"start": "next start",
1818
"lint": "eslint --ignore-path .gitignore .",
19-
"format": "prettier --ignore-path .gitignore --write \"**/*.+(ts|tsx|json)\""
19+
"format": "prettier --ignore-path .gitignore --write \"**/*.+(ts|tsx|json)\"",
20+
"test": "npx jest",
21+
"test:watch": "yarn test --watch"
2022
},
2123
"dependencies": {
2224
"next": "10.0.0",
@@ -25,17 +27,26 @@
2527
"styled-components": "^5.2.1"
2628
},
2729
"devDependencies": {
30+
"@testing-library/dom": "^7.29.6",
31+
"@testing-library/jest-dom": "^5.11.9",
32+
"@testing-library/react": "^11.2.5",
33+
"@testing-library/user-event": "^12.8.0",
34+
"@types/faker": "^5.1.7",
2835
"@types/gtag.js": "^0.0.3",
2936
"@types/node": "^14.14.6",
3037
"@types/react": "^16.9.55",
3138
"@types/styled-components": "^5.1.4",
3239
"@typescript-eslint/eslint-plugin": "^4.6.0",
3340
"@typescript-eslint/parser": "^4.6.0",
41+
"babel-jest": "^26.6.3",
3442
"eslint": "^7.12.1",
3543
"eslint-config-prettier": "^6.15.0",
3644
"eslint-plugin-jsx-a11y": "^6.4.1",
3745
"eslint-plugin-react": "^7.21.5",
3846
"eslint-plugin-react-hooks": "^4.2.0",
47+
"faker": "^5.4.0",
48+
"jest": "^26.6.3",
49+
"jest-dom": "^4.0.0",
3950
"prettier": "^2.1.2",
4051
"typescript": "^4.1.0-beta"
4152
}

pages/__tests__/index.test.tsx

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import f from 'faker';
2+
import user from '@testing-library/user-event';
3+
import { screen } from '@testing-library/react';
4+
5+
import { Locale } from 'i18n';
6+
import { renderWithRoute } from 'tests/wrappers';
7+
import Home, { Post, SOCIAL_MEDIAS } from '../index';
8+
9+
import * as gtag from 'lib/gtag';
10+
11+
function mockPosts(): Post[] {
12+
const count = f.random.number({ min: 2, max: 10 });
13+
const posts = new Array(count).fill('').map(() => ({
14+
url: f.internet.url(),
15+
slug: f.lorem.slug(),
16+
title: f.name.title(),
17+
description: f.lorem.words(10),
18+
}));
19+
20+
return posts;
21+
}
22+
23+
const mockedEvent = jest.spyOn(gtag, 'event');
24+
25+
beforeEach(() => {
26+
mockedEvent.mockImplementation(jest.fn);
27+
});
28+
29+
afterEach(() => {
30+
mockedEvent.mockClear();
31+
});
32+
33+
afterAll(() => {
34+
mockedEvent.mockRestore();
35+
});
36+
37+
describe('Takah.dev', () => {
38+
it('should render accordingly', () => {
39+
const locale = Locale.enUS;
40+
const posts = mockPosts();
41+
const { t } = renderWithRoute(<Home posts={posts} />, {
42+
routeOptions: { locale },
43+
});
44+
45+
const welcomeHeading = screen.getByRole('heading', {
46+
name: t('welcome'),
47+
level: 1,
48+
});
49+
expect(welcomeHeading).toBeInTheDocument();
50+
51+
const bioDescription = screen.getByText(t('bio'));
52+
expect(bioDescription).toBeInTheDocument();
53+
54+
const authorImage = screen.getByAltText(t('authorImageAlt'));
55+
expect(authorImage).toBeInTheDocument();
56+
57+
Object.values(SOCIAL_MEDIAS).forEach(({ alt, src, link }) => {
58+
const socialMediaLink = screen.getByRole('link', { name: alt });
59+
expect(socialMediaLink).toBeInTheDocument();
60+
expect(socialMediaLink).toHaveAttribute('href', link);
61+
62+
const socialMediaImage = screen.getByAltText(`${alt} icon`);
63+
expect(socialMediaImage).toBeInTheDocument();
64+
expect(socialMediaImage).toHaveAttribute('src', src);
65+
});
66+
67+
const blogPostsHeading = screen.getByRole('heading', {
68+
name: 'Blog posts',
69+
level: 2,
70+
});
71+
expect(blogPostsHeading).toBeInTheDocument();
72+
73+
posts.forEach(({ title, url }) => {
74+
const postLink = screen.getByRole('link', { name: title });
75+
expect(postLink).toBeInTheDocument();
76+
expect(postLink).toHaveAttribute('href', url);
77+
});
78+
79+
const footer = screen.getByText(t('footer'));
80+
expect(footer).toBeInTheDocument();
81+
});
82+
83+
describe('Social Media', () => {
84+
it('should navigate and track accordingly', () => {
85+
const locale = Locale.enUS;
86+
const posts = mockPosts();
87+
renderWithRoute(<Home posts={posts} />, { routeOptions: { locale } });
88+
89+
const socialMediaLabel = f.random.arrayElement(
90+
Object.keys(SOCIAL_MEDIAS)
91+
);
92+
93+
const socialMedia = SOCIAL_MEDIAS[socialMediaLabel];
94+
95+
const socialMediaLink = screen.getByRole('link', {
96+
name: socialMedia.alt,
97+
});
98+
expect(socialMediaLink).toBeInTheDocument();
99+
expect(socialMediaLink).toHaveAttribute('href', socialMedia.link);
100+
101+
user.click(socialMediaLink);
102+
103+
expect(mockedEvent).toHaveBeenCalledWith({
104+
action: 'social_click',
105+
category: 'Social media',
106+
label: socialMediaLabel,
107+
});
108+
});
109+
});
110+
describe('Blog Posts', () => {
111+
it('should navigate and track accordingly', () => {
112+
const locale = Locale.enUS;
113+
const posts = mockPosts();
114+
renderWithRoute(<Home posts={posts} />, { routeOptions: { locale } });
115+
116+
const post = f.random.arrayElement(posts);
117+
118+
const postLink = screen.getByRole('link', {
119+
name: post.title,
120+
});
121+
expect(postLink).toBeInTheDocument();
122+
expect(postLink).toHaveAttribute('href', post.url);
123+
124+
user.click(postLink);
125+
126+
expect(mockedEvent).toHaveBeenCalledWith({
127+
action: 'post_click',
128+
category: 'Post click',
129+
label: post.slug,
130+
});
131+
});
132+
});
133+
});

0 commit comments

Comments
 (0)