Skip to content

Commit e6fbea5

Browse files
Merge pull request #24 from aceol/prepare-for-testing
Prepare for testing
2 parents 7931fad + a209358 commit e6fbea5

File tree

7 files changed

+679
-33
lines changed

7 files changed

+679
-33
lines changed

.github/workflows/tests.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
run-tests:
7+
name: Run tests
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Check out Git repository
12+
uses: actions/checkout@v4
13+
14+
- name: Set up Node.js
15+
uses: actions/setup-node@v4
16+
with:
17+
cache: 'npm'
18+
19+
- name: Run tests
20+
run: npm t

index.html

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
</head>
1616

1717
<body>
18-
<dialog id="settingsModal" onsubmit="submitSettings()">
18+
<dialog id="settingsModal">
1919
<h3>Settings</h3>
2020
<form id="settingsForm" method="dialog" name="settingsForm">
21-
<button id="closeSettings" class="btn" onclick="hideSettings()"></button>
21+
<button id="closeSettings" class="btn"></button>
2222

2323
<label for="durationMinutes">Duration :</label>
2424
<div>
@@ -66,19 +66,24 @@ <h3>Settings</h3>
6666
</div>
6767
</form>
6868
<div class="buttons">
69-
<button id="resetBtn" class="action" onclick="resetDefaultSettings()">Restore defaults</button>
70-
<button id="submitBtn" class="action" onclick="submitSettings()">Apply</button>
69+
<button id="resetBtn" class="action">Restore defaults</button>
70+
<button id="submitBtn" class="action">Apply</button>
7171
</div>
7272

7373
</dialog>
7474

75-
<div id="startMessage"><button class="btn" onclick="startAnimation()">START</button></div>
75+
<div id="startMessage"><button id="startAnimation" class="btn">START</button></div>
7676
<div id="timer">00:00</div>
7777
<div id="expandingDiv"></div>
78-
<div id="toggleSettings"><button class="btn" onclick="showSettings()">⚙️</button></div>
78+
<div id="toggleSettings"><button id="showSettings" class="btn">⚙️</button></div>
7979
<div id="credits">made with ❤️ by Zenika</div>
8080

81-
<script src="js/app.js"></script>
81+
<script src="./js/utils.mjs" type="module"></script>
82+
<script type="module">
83+
import { init } from "./js/app.mjs";
84+
init();
85+
</script>
86+
8287

8388
</body>
8489

js/app.js renamed to js/app.mjs

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
// fetch params in url for quick settings
2+
import { updateTimer } from "./utils.mjs";
3+
4+
// Lecture du paramètre "duration" dans l'URL (en secondes)
25
const params = new URLSearchParams(globalThis.location.search);
36
const div = document.getElementById('expandingDiv');
47
const timer = document.getElementById('timer');
58
const startMessage = document.getElementById('startMessage');
6-
const settingsModal = document.getElementById("settingsModal");
79
const settingsForm = document.getElementById("settingsForm");
10+
const settingsModal = document.getElementById("settingsModal");
11+
const closeSettings = document.getElementById("closeSettings");
12+
const resetBtn = document.getElementById("resetBtn");
13+
const submitBtn = document.getElementById("submitBtn");
14+
const showSettingsBtn = document.getElementById("showSettings");
15+
const startAnimationBtn = document.getElementById("startAnimation");
816
const totalHeight = window.innerHeight;
917

1018
const defaultSettings = {
11-
"durationInSeconds" : 600,
19+
"durationInSeconds": 600,
1220
"showTimer": true,
1321
"colorScheme": "zenika-colors",
1422
"firstThreshold": 0.8,
@@ -19,7 +27,7 @@ const defaultSettings = {
1927
}
2028

2129
let settings = {
22-
"durationInSeconds" : parseDuration(params.get("duration")),
30+
"durationInSeconds": parseDuration(params.get("duration")),
2331
"showTimer": true,
2432
"colorScheme": "zenika-colors",
2533
"firstThreshold": 0.8,
@@ -86,7 +94,7 @@ function submitSettings() {
8694
}
8795

8896
function applySettings() {
89-
updateTimer(settings.durationInSeconds);
97+
timer.textContent = updateTimer(settings.durationInSeconds);
9098
document.documentElement.className = settings.colorScheme;
9199

92100
if (settings.showTimer === false) {
@@ -120,16 +128,6 @@ function resetDefaultSettings() {
120128
}
121129

122130
// ------------- Animation ----------------
123-
124-
function updateTimer(secRemaining) {
125-
const sec = Math.max(0, Math.ceil(secRemaining));
126-
const minutes = Math.floor(sec / 60);
127-
const remainingSeconds = sec % 60;
128-
129-
// format into "mm:ss" padded with 0 if needed
130-
timer.textContent = `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
131-
}
132-
133131
function getClassByProgress(p) {
134132
// p = percentage between 0 and 1
135133
if (p < settings.firstThreshold) return 'start';
@@ -138,7 +136,6 @@ function getClassByProgress(p) {
138136
return 'ending';
139137
}
140138

141-
142139
function startAnimation() {
143140
requestWakeLock();
144141
startMessage.style.opacity = 0;
@@ -150,8 +147,9 @@ function startAnimation() {
150147
const elapsed = time - startTime;
151148
const progress = Math.min(elapsed / (settings.durationInSeconds * 1000), 1); // 0 → 1
152149
const currentHeight = Math.floor(totalHeight * progress);
150+
153151
const remaining = settings.durationInSeconds - (elapsed / 1000);
154-
updateTimer(remaining);
152+
timer.textContent = updateTimer(remaining);
155153

156154
div.style.height = `${currentHeight}px`;
157155
div.classList = getClassByProgress(progress);
@@ -207,11 +205,24 @@ function playBeep() {
207205
oscillator.stop(audioCtx.currentTime + 0.3); // play for 0.3s
208206
}
209207

210-
// Wakelock: réactiver si la page revient au premier plan
211-
document.addEventListener('visibilitychange', () => {
212-
if (wakeLock !== null && document.visibilityState === 'visible') {
213-
requestWakeLock();
214-
}
215-
});
216208

217-
applySettings();
209+
export function init() {
210+
// Wakelock: réactiver si la page revient au premier plan
211+
document.addEventListener('visibilitychange', () => {
212+
if (wakeLock !== null && document.visibilityState === 'visible') {
213+
requestWakeLock();
214+
}
215+
});
216+
217+
settingsForm.addEventListener('submit', submitSettings);
218+
closeSettings.addEventListener('click', closeSettings);
219+
resetBtn.addEventListener('click', resetDefaultSettings);
220+
submitBtn.addEventListener('click', submitSettings);
221+
startAnimationBtn.addEventListener('click', startAnimation);
222+
showSettingsBtn.addEventListener('click', showSettings);
223+
224+
225+
226+
applySettings();
227+
}
228+

js/tests/app.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { describe, it } from "node:test";
2+
import assert from "node:assert";
3+
import { updateTimer } from "../utils.mjs";
4+
5+
describe('Update timer', () => {
6+
[
7+
{ timer: '00:00', time: 0 },
8+
{ timer: '00:02', time: 2 },
9+
{ timer: '01:01', time: 61 },
10+
{ timer: '1440:01', time: 86401 }
11+
].forEach(({ timer, time }) => {
12+
it(`with ${time} displays ${timer}`, () => {
13+
assert.equal(updateTimer(time), timer);
14+
})
15+
})
16+
});

js/utils.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function updateTimer(secRemaining) {
2+
const sec = Math.max(0, Math.ceil(secRemaining));
3+
const minutes = Math.floor(sec / 60);
4+
const remainingSeconds = sec % 60;
5+
// format into "mm:ss" padded with 0 if needed
6+
return `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
7+
}

0 commit comments

Comments
 (0)