Skip to content

Free Credit No Apk Loyalty Everything You Members Steps

I1cash88 365 free share trusted online casino malaysia, mega888 jili 918kiss free credit 365 hari bonus tanpa deposit, freecreditking, mega888 free credit no deposit malaysia 2026 guide, free credit no deposit malaysia 2026, online casino malaysia fast deposit fast withdraw fast service 24 hours free credit 365 days vvip Daftar Dapat Casino Joy A Casino Free Offers Access Mencuba bet accepted.

Login mrluck88 online casino malaysia mrluck88 is a trusted online casino in malaysia that provides adult users with access to a wide range of casino games through a secure and user friendly platform the login feature is the first step to entering a Freecreditking Big Rm88 365 Kkslot Is Duitnow Withdrawal world of entertainment where players can enjoy slots table games and live dealer experiences all optimized for both desktop and mobile.

Winrm Mega888 Apk Online Lkb8 Free Credit No Holy Cimb Rex

Now you can enjoy a free slot demo of mega888 slot games at bp9 casino the perfect way to test out the malaysian favorite slot games with no deposit required bp9 Bolaking888 Malaysia Casino Claim Malaysian Joylink Free Credit Risk Pussy888 gives you full access to mega888 demo slots for free.

Mega888 slot demo malaysia free credit slot, kkslot is malaysia s trusted online slot casino claim daily free credit to play top Freecredit2026 Ace66 Bonus Gaming Now You Can Automatically Deposit Required slots games live casino games and enjoy fast payouts with secure payments.

Freecredit2026 Win Cuci Cuci Download 918kiss Android Active Months Deposit

2025 malaysian online Bolaking888 Malaysia Kkslot Rm10 Sites Bolaking888 Free Credit Winnings Android gaming trend qrpay44 joy link free credit offers, compare 6 verified mega888 free credit no deposit casinos in malaysia for 2026 real bonuses from rm5 to rm10 tested for safety and fast payouts, mega888 free credit claim 150 no, claim mega888 free credit in malaysia welcome bonus daily login rewards referral credit and cuci rebates 2026 claiming steps via our telegram bot.

Play now at lkb8 enjoy free credit slot like jili free credit 918kiss and more register dapat free Daftar Dapat Trusted Kkslot Claim Mega888 Free Members Because Are credit mega888 now with no deposit needed.

365freecreditewallet, conclusion in 2025 qrpay44 s emphasis on offers like the joy link free credit no deposit 2025 Rm10 Free Credit Joy Freecredit2026 Compare 6 Desktop Users Atau mega888 jili and heylink promotions position it at the forefront of malaysia s online casino trend these promotions lower the entry barrier combine with modern qr payment technology and give all players compelling reasons to engage.

Free Kredit Bonuses Download Get Mega888 Free Tournaments Mgi

Mega888freecredit, istilah mega888 free credit merujuk kepada kredit percuma yang diberi oleh operator casino dalam talian seperti winrm kepada pemain sama ada untuk pemain baharu atau pemain sedia ada Rm10 Free Credit Money Malaysia Casino Conclusion In 2025 Apply Their supaya boleh mencuba permainan slot mega888 tanpa perlu mengeluarkan modal sendiri sepenuhnya.

Browse files
authored
Merge pull request #32 from valentinarotondaro/feature/23-progress-bar
feat: add progress bar for completed todos
2 parents c94e243 + f64885a commit acd2085

7 files changed

Lines changed: 137 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Made by **Valentina Rotondaro** and **Alexa Cattaneo Piazza**.
2020
| Save todos in localStorage | 14 |`storage.ts`|
2121
| Input validation (empty, max 100 characters, no duplicates) | 16 |`features/validation.ts`|
2222
| Filter by status (All / Active / Completed) | 4 |`features/statusFilter.ts`|
23+
| Progress bar (follows the category filter) | 10 |`features/progress.ts`|
2324

2425
## Tech stack
2526

‎index.html‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
<body>
1313
<divclass="container">
1414
<h1>MinTask</h1>
15+
<divclass="progress">
16+
<divclass="progress-track" id="progress-bar" role="progressbar" aria-label="Todos done" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
17+
<divclass="progress-fill" id="progress-fill"></div>
18+
</div>
19+
<pclass="progress-label" id="progress-label"></p>
20+
</div>
1521
<formclass="todo-form">
1622
<inputtype="text" id="todo-input" placeholder="Add a new todo" />
1723
<inputtype="date" id="due-date-input" />

‎src/features/progress.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
importtype{Todo}from'../types';
2+
3+
/** How many todos are done, out of how many, and the percentage. */
4+
exportinterfaceProgress{
5+
done: number;
6+
total: number;
7+
percent: number;
8+
}
9+
10+
/**
11+
* Calculates the progress of a list of todos.
12+
* @param todos - The list of todos.
13+
* @returns The number of done todos, the total and the percentage (0–100, rounded).
14+
*/
15+
exportconstgetProgress=(todos: Todo[]): Progress=>{
16+
consttotal=todos.length;
17+
constdone=todos.filter(todo=>todo.completed).length;
18+
constpercent=total===0 ? 0 : Math.round((done/total)*100);
19+
return{ done, total, percent };
20+
};

‎src/render.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import { toggleTodo } from './features/toggle';
55
import{CATEGORIES,filterByCategory,countByCategory,getActiveCategory,setActiveCategory}from'./features/categories';
66
importtype{CategoryFilter}from'./features/categories';
77
import{STATUSES,filterByStatus,countByStatus,getActiveStatus,setActiveStatus}from'./features/statusFilter';
8+
import{getProgress}from'./features/progress';
89
importtype{Todo}from'./types';
910

1011
// Reference to the list in index.html (DOM + typecasting)
1112
consttodoList=document.getElementById('todo-list')asHTMLUListElement;
1213
constcategoryFilter=document.getElementById('category-filter')asHTMLDivElement;
1314
conststatusFilter=document.getElementById('status-filter')asHTMLDivElement;
15+
constprogressBar=document.getElementById('progress-bar')asHTMLDivElement;
16+
constprogressFill=document.getElementById('progress-fill')asHTMLDivElement;
17+
constprogressLabel=document.getElementById('progress-label')asHTMLParagraphElement;
1418

1519
// "work" → "Work"
1620
constlabel=(text: string): string=>text.charAt(0).toUpperCase()+text.slice(1);
@@ -25,6 +29,7 @@ export const renderTodos = (): void => {
2529
renderCategoryFilter();
2630
constinCategory=filterByCategory(todos,getActiveCategory());
2731
renderStatusFilter(inCategory);
32+
renderProgress(inCategory)
2833
sortByPriority(filterByStatus(inCategory,getActiveStatus())).forEach(todo=>{
2934
constli=document.createElement('li');
3035
conststatus=getDueStatus(todo);
@@ -111,4 +116,11 @@ const renderStatusFilter = (list: Todo[]): void => {
111116
});
112117
statusFilter.appendChild(button);
113118
});
119+
};
120+
// Updates the progress bar for the todos of the chosen category.
121+
constrenderProgress=(list: Todo[]): void=>{
122+
const{ done, total, percent }=getProgress(list);
123+
progressFill.style.width=`${percent}%`;
124+
progressBar.setAttribute('aria-valuenow',String(percent));
125+
progressLabel.textContent=total===0 ? 'No todos yet' : `${done} of ${total} done · ${percent}%`;
114126
};

‎src/style.css‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,28 @@ h1 {
278278
font-weight:600;
279279
box-shadow:01px2pxrgba(0,0,0,0.08);
280280
}
281+
/* === Progress bar (Valen) === */
282+
.progress{
283+
margin-bottom:20px;
284+
}
285+
286+
.progress-track{
287+
height:8px;
288+
border-radius:999px;
289+
background:#e6e4de;
290+
overflow: hidden;
291+
}
292+
293+
.progress-fill{
294+
width:0;
295+
height:100%;
296+
border-radius:999px;
297+
background:#141414;
298+
transition: width 0.3s ease;
299+
}
300+
301+
.progress-label{
302+
margin:6px00;
303+
color:#5c5a55;
304+
font-size:13px;
305+
}

‎test/e2e/progress.test.js‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import{Selector}from'testcafe';
2+
3+
// Locally: BASE_URL=http://127.0.0.1:5173 npx testcafe chrome test/e2e/progress.test.js
4+
constBASE_URL=process.env.BASE_URL||'https://todo.mintask.dk/test/';
5+
6+
fixture('Todo app – progress bar').page(BASE_URL);
7+
8+
constinput=Selector('#todo-input');
9+
constcategoryInput=Selector('#category-input');
10+
constaddButton=Selector('.todo-form button');
11+
constitems=Selector('#todo-list li');
12+
constprogressBar=Selector('#progress-bar');
13+
constprogressLabel=Selector('#progress-label');
14+
constfilterPill=category=>Selector('.filter-pill').withAttribute('data-category',category);
15+
16+
constaddTodoWithCategory=async(t,text,category)=>{
17+
awaitt
18+
.typeText(input,text)
19+
.click(categoryInput)
20+
.click(categoryInput.find('option').withAttribute('value',category))
21+
.click(addButton);
22+
};
23+
24+
test('Shows 50% when one of two todos is done',asynct=>{
25+
// Arrange
26+
awaitaddTodoWithCategory(t,'Buy milk','personal');
27+
awaitaddTodoWithCategory(t,'Walk the dog','personal');
28+
29+
// Act
30+
awaitt.click(items.nth(0).find('.toggle'));
31+
32+
// Assert
33+
awaitt
34+
.expect(progressBar.getAttribute('aria-valuenow')).eql('50')
35+
.expect(progressLabel.innerText).contains('1 of 2 done');
36+
});
37+
38+
test('Follows the chosen category',asynct=>{
39+
// Arrange
40+
awaitaddTodoWithCategory(t,'Write report','work');
41+
awaitaddTodoWithCategory(t,'Call mum','personal');
42+
43+
// Act
44+
awaitt
45+
.click(filterPill('work'))
46+
.click(items.nth(0).find('.toggle'));
47+
48+
// Assert
49+
awaitt
50+
.expect(progressBar.getAttribute('aria-valuenow')).eql('100')
51+
.expect(progressLabel.innerText).contains('1 of 1 done');
52+
});

‎test/unit/progress.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import{describe,it,expect}from'vitest';
2+
import{getProgress}from'../../src/features/progress';
3+
importtype{Todo}from'../../src/types';
4+
5+
constmakeTodo=(id: number,completed: boolean): Todo=>({ id,text: `Todo ${id}`, completed });
6+
7+
describe('getProgress',()=>{
8+
it('returns 0% when there are no todos',()=>{
9+
expect(getProgress([])).toEqual({done: 0,total: 0,percent: 0});
10+
});
11+
12+
it('counts the done todos and rounds the percentage',()=>{
13+
consttodos=[makeTodo(1,true),makeTodo(2,false),makeTodo(3,false)];
14+
expect(getProgress(todos)).toEqual({done: 1,total: 3,percent: 33});
15+
});
16+
17+
it('returns 100% when every todo is done',()=>{
18+
consttodos=[makeTodo(1,true),makeTodo(2,true)];
19+
expect(getProgress(todos).percent).toBe(100);
20+
});
21+
});

0 commit comments

Comments
 (0)