Skip to content

Commit d37039a

Browse files
authored
Merge pull request #28 from tfeijo/test/mocks-and-tests
Criação de testes unitários e de integração
2 parents e236998 + 9e46811 commit d37039a

16 files changed

+8077
-1846
lines changed

.github/workflows/test.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Run Tests
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v3
15+
16+
- name: Setup Node.js
17+
uses: actions/setup-node@v3
18+
with:
19+
node-version: '18.20.8'
20+
21+
- name: Cache Node.js modules
22+
uses: actions/cache@v3
23+
with:
24+
path: node_modules
25+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
26+
restore-keys: |
27+
${{ runner.os }}-node-
28+
29+
- name: Install dependencies
30+
run: npm install
31+
32+
- name: Run tests
33+
run: npm test

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
1-
# Timer criado durante gravação de vídeo no Youtube
1+
# ⏱️ Timer criado durante gravação de vídeo no YouTube
22

33
Este é um aplicativo simples de timer que foi usado durante a [gravação deste vídeo](https://www.youtube.com/watch?v=ir8MIBhGbcA).
44

55
O layout está [aqui no Figma](https://www.figma.com/design/97maginjN0aHjiQPy3dCDS/%231---Timer?m=auto&t=B8ND36dunZtQcZQe-1).
6+
7+
---
8+
9+
## 🚀 Como executar a aplicação
10+
11+
1. Instale as dependências:
12+
```bash
13+
npm install
14+
```
15+
16+
2. Inicie o ambiente de desenvolvimento:
17+
```bash
18+
npm run dev
19+
```
20+
21+
3. Acesse no navegador:
22+
```
23+
http://localhost:1234
24+
```
25+
26+
---
27+
28+
## 🧪 Como executar os testes
29+
30+
Para rodar os testes automatizados com Jest:
31+
32+
```bash
33+
npm test
34+
```
35+
36+
Ou:
37+
38+
```bash
39+
npm run test
40+
```

asset/javascript/TimeController.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,5 @@ document.addEventListener('DOMContentLoaded', function () {
357357
const timerController = new TimerController(reference);
358358
window.timerController = timerController;
359359
});
360+
361+
export default TimerController;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { jest } from '@jest/globals';
2+
3+
// Mock de dependências externas
4+
jest.mock('../../sound/tick-tack.wav', () => 'tick-tack.wav');
5+
jest.mock('../../sound/stop.mp3', () => 'stop.mp3');
6+
jest.mock('../helper.js', () => ({}), { virtual: true });
7+
8+
// Mock do TimerStatus e TimeUtils
9+
jest.mock('../TimerStatus.js', () => ({
10+
TimerStatus: {
11+
STOPPED: 'STOPPED',
12+
PAUSED: 'PAUSED',
13+
COUNTDOWN: 'COUNTDOWN',
14+
RUNNING: 'RUNNING',
15+
EDITING: 'EDITING',
16+
isRunning: jest.fn(),
17+
isCountdown: jest.fn(),
18+
isPaused: jest.fn(),
19+
isStopped: jest.fn(),
20+
}
21+
}));
22+
23+
jest.mock('../TimeUtils.js', () => ({
24+
formatTimeUnit: jest.fn(v => String(v).padStart(2, '0').slice(-2)),
25+
hourToSeconds: jest.fn(h => h * 3600),
26+
minuteToSeconds: jest.fn(m => m * 60),
27+
remainingSeconds: jest.fn(s => s % 60),
28+
secondsToHour: jest.fn(s => Math.floor(s / 3600)),
29+
secondsToMinute: jest.fn(s => Math.floor((s % 3600) / 60)),
30+
}));
31+
function createMockInput() {
32+
return {
33+
value: '00',
34+
addEventListener: jest.fn(),
35+
focus: jest.fn(),
36+
disabled: false,
37+
};
38+
}
39+
function createMockButton() {
40+
return {
41+
addEventListener: jest.fn(),
42+
showElement: jest.fn(),
43+
hideElement: jest.fn(),
44+
focus: jest.fn(),
45+
};
46+
}
47+
function createMockContainer() {
48+
return {
49+
querySelector: jest.fn((selector) => {
50+
if (selector.includes('input')) return createMockInput();
51+
if (selector.includes('button')) return createMockButton();
52+
if (selector.includes('number')) return { textContent: '', classList: { add: jest.fn(), remove: jest.fn() } };
53+
return {};
54+
}),
55+
showElement: jest.fn(),
56+
hideElement: jest.fn(),
57+
classList: { add: jest.fn(), remove: jest.fn() },
58+
};
59+
}
60+
61+
export { createMockInput, createMockButton, createMockContainer };
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const formatTimeUnit = jest.fn(value => value.toString().padStart(2, '0'));
2+
export const hourToSeconds = jest.fn(hours => hours * 3600);
3+
export const minuteToSeconds = jest.fn(minutes => minutes * 60);
4+
export const remainingSeconds = jest.fn(seconds => seconds % 60);
5+
export const secondsToHour = jest.fn(seconds => Math.floor(seconds / 3600));
6+
export const secondsToMinute = jest.fn(seconds => Math.floor((seconds % 3600) / 60));
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
global.Audio = jest.fn().mockImplementation(() => ({
2+
play: jest.fn(),
3+
pause: jest.fn(),
4+
addEventListener: jest.fn(),
5+
volume: 0,
6+
loop: false,
7+
currentTime: 0,
8+
}));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Object.defineProperty(document, 'fullscreenElement', {
2+
value: null,
3+
writable: true,
4+
});
5+
6+
document.documentElement.requestFullscreen = jest.fn();
7+
document.exitFullscreen = jest.fn();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const mockElement = (() => {
2+
const cache = {};
3+
4+
return () => ({
5+
querySelector: jest.fn(selector => {
6+
if (!cache[selector]) {
7+
if (String(selector).includes('.js-*-button')) {
8+
cache[selector] = {
9+
addEventListener: jest.fn(),
10+
hideElement: jest.fn(),
11+
showElement: jest.fn(),
12+
};
13+
} else {
14+
cache[selector] = mockElement();
15+
}
16+
}
17+
return cache[selector];
18+
}),
19+
addEventListener: jest.fn(),
20+
removeEventListener: jest.fn(),
21+
focus: jest.fn(),
22+
hideElement: jest.fn(),
23+
showElement: jest.fn(),
24+
classList: {
25+
add: jest.fn(),
26+
remove: jest.fn(),
27+
},
28+
value: '00',
29+
textContent: '',
30+
disabled: false,
31+
});
32+
})();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 'test-file-stub';
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import TimerController from '../TimeController';
2+
import * as TimeUtils from '../TimeUtils';
3+
4+
jest.mock('../TimeUtils');
5+
6+
describe('TimerController', () => {
7+
let mockReference;
8+
9+
beforeEach(() => {
10+
mockReference = {
11+
querySelector: jest.fn(selector => {
12+
if (String(selector).includes('input')) {
13+
return {
14+
addEventListener: jest.fn(),
15+
value: '00',
16+
disabled: false,
17+
};
18+
}
19+
if (selector === '.js-stopwatch-action-buttons') {
20+
return {
21+
querySelector: jest.fn(innerSelector => {
22+
if (innerSelector === '.js-start-button') {
23+
return {
24+
addEventListener: jest.fn(),
25+
hideElement: jest.fn(),
26+
showElement: jest.fn(),
27+
};
28+
}
29+
return {
30+
addEventListener: jest.fn(),
31+
hideElement: jest.fn(),
32+
showElement: jest.fn(),
33+
};
34+
}),
35+
hideElement: jest.fn(),
36+
showElement: jest.fn(),
37+
};
38+
}
39+
if (selector === '.js-edit-container-stopwatch') {
40+
return {
41+
querySelector: jest.fn(innerSelector => ({
42+
addEventListener: jest.fn(),
43+
hideElement: jest.fn(),
44+
showElement: jest.fn(),
45+
})),
46+
hideElement: jest.fn(),
47+
showElement: jest.fn(),
48+
};
49+
}
50+
if (selector === '.js-countdown-container') {
51+
return {
52+
querySelector: jest.fn(innerSelector => {
53+
if (innerSelector === '.js-close-countdown-button') {
54+
return {
55+
addEventListener: jest.fn(),
56+
};
57+
}
58+
return {
59+
textContent: '',
60+
};
61+
}),
62+
hideElement: jest.fn(),
63+
showElement: jest.fn(),
64+
classList: {
65+
add: jest.fn(),
66+
remove: jest.fn(),
67+
},
68+
textContent: '',
69+
};
70+
}
71+
if (selector === '.js-stopwatch-action-buttons') {
72+
return {
73+
querySelector: jest.fn(innerSelector => {
74+
if (innerSelector === '.js-start-button') {
75+
const mockAddEventListener = jest.fn();
76+
return {
77+
addEventListener: mockAddEventListener,
78+
hideElement: jest.fn(),
79+
showElement: jest.fn(),
80+
};
81+
}
82+
if (innerSelector === '.js-pause-button') {
83+
return {
84+
addEventListener: jest.fn(),
85+
hideElement: jest.fn(),
86+
showElement: jest.fn(),
87+
};
88+
}
89+
return {
90+
addEventListener: jest.fn(),
91+
hideElement: jest.fn(),
92+
showElement: jest.fn(),
93+
};
94+
}),
95+
hideElement: jest.fn(),
96+
showElement: jest.fn(),
97+
};
98+
}
99+
return {
100+
addEventListener: jest.fn(),
101+
hideElement: jest.fn(),
102+
showElement: jest.fn(),
103+
focus: jest.fn(),
104+
value: '00',
105+
classList: { add: jest.fn(), remove: jest.fn() },
106+
};
107+
}),
108+
};
109+
});
110+
111+
112+
it('deve inicializar corretamente', () => {
113+
const timerController = new TimerController(mockReference);
114+
expect(mockReference.querySelector).toHaveBeenCalledWith('.js-hour-input');
115+
});
116+
117+
it('deve formatar corretamente os valores de tempo', () => {
118+
TimeUtils.formatTimeUnit.mockReturnValue('05');
119+
const result = TimeUtils.formatTimeUnit(5);
120+
expect(result).toBe('05');
121+
expect(TimeUtils.formatTimeUnit).toHaveBeenCalledWith(5);
122+
});
123+
});

0 commit comments

Comments
 (0)