Skip to content

Commit 8682c1e

Browse files
authored
Merge pull request #767 from IT-Academy-BCN/feature/176-filters-logic-integration
1/2 feature(starter): Filtering components logic integration #195
2 parents 75e7ba5 + 3ad8e1a commit 8682c1e

File tree

9 files changed

+120
-56
lines changed

9 files changed

+120
-56
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44
and this project adheres to
55
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
### [ita-challenges-frontend-3.31.0-RELEASE] - 2026-02-19
8+
9+
### Added
10+
- Functionality for new filtering components.
11+
### Removed
12+
- Visibility for old filtering component.
13+
714
### [ita-challenges-frontend-3.30.0-RELEASE] - 2026-02-20
815
### Added
916
- Restyled challenge detail page with card layout, repositioned breadcrumb outside header card, and moved difficulty indicator to stats row

conf/.env.CI.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
MICROSERVICE_DEPLOY=ita-challenges-frontend
2-
MICROSERVICE_VERSION=3.30.0-RELEASE
2+
MICROSERVICE_VERSION=3.31.0-RELEASE

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ita-challenges-frontend",
3-
"version": "3.30.0-RELEASE",
3+
"version": "3.31.0-RELEASE",
44
"scripts": {
55
"ng": "ng",
66
"start": "ng serve --proxy-config proxy.conf.dev.json",

src/app/modules/starter/components/challenge-list-filters/challenge-list-filters.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<div id="challenge-list-filters">
2-
<div><app-language-filter></app-language-filter></div>
2+
<div><app-language-filter
3+
(languageSelected)="onLanguageFilterChange($event)"></app-language-filter></div>
34
<div class="space-divider"></div>
45
<app-sort-select
56
[sortBy]="sortBy"
Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,87 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing'
1+
import { ComponentFixture, TestBed } from "@angular/core/testing";
22

3-
import { ChallengeListFiltersComponent } from './challenge-list-filters.component'
3+
import { ChallengeListFiltersComponent } from "./challenge-list-filters.component";
4+
import { FilterChallenge } from "src/app/models/filter-challenge.model";
5+
import { SolutionStatus } from "src/app/models/user-solution-status.enum";
46

5-
describe('ChallengeListFiltersComponent', () => {
6-
let component: ChallengeListFiltersComponent
7-
let fixture: ComponentFixture<ChallengeListFiltersComponent>
7+
describe("ChallengeListFiltersComponent", () => {
8+
let component: ChallengeListFiltersComponent;
9+
let fixture: ComponentFixture<ChallengeListFiltersComponent>;
10+
let AllFiltersAppliedSpy: jasmine.Spy;
11+
let SortSelectedSpy: jasmine.Spy;
12+
let OrderSelectedSpy: jasmine.Spy;
13+
let initialFilters: FilterChallenge = {
14+
languages: [],
15+
levels: [],
16+
progress: [],
17+
tags: [],
18+
};
819

920
beforeEach(async () => {
1021
await TestBed.configureTestingModule({
11-
declarations: [ChallengeListFiltersComponent]
12-
}).compileComponents()
22+
declarations: [ChallengeListFiltersComponent],
23+
}).compileComponents();
1324

14-
fixture = TestBed.createComponent(ChallengeListFiltersComponent)
15-
component = fixture.componentInstance
16-
fixture.detectChanges()
17-
})
25+
fixture = TestBed.createComponent(ChallengeListFiltersComponent);
26+
component = fixture.componentInstance;
27+
fixture.detectChanges();
28+
});
1829

19-
it('should create', () => {
20-
expect(component).toBeTruthy()
21-
})
30+
it("should create", () => {
31+
expect(component).toBeTruthy();
32+
});
2233

23-
it('should render the filters container', () => {
24-
const element: HTMLElement = fixture.nativeElement
25-
expect(element.querySelector('#challenge-list-filters')).toBeTruthy()
26-
})
27-
})
34+
it("should render the filters container", () => {
35+
const element: HTMLElement = fixture.nativeElement;
36+
expect(element.querySelector("#challenge-list-filters")).toBeTruthy();
37+
});
38+
it("should have initial filters set correctly", () => {
39+
component.initialFilters = {
40+
languages: ["1234"],
41+
levels: [],
42+
progress: [],
43+
tags: [],
44+
};
45+
expect(component.initialFilters).toEqual({
46+
languages: ["1234"],
47+
levels: [],
48+
progress: [],
49+
tags: [],
50+
});
51+
});
52+
53+
it("should emit allFiltersApplied with correct filters when onModalFiltersApplied is called", () => {
54+
AllFiltersAppliedSpy = spyOn(component.allFiltersApplied, "emit");
55+
const modalFilters = {
56+
levels: ["Beginner"],
57+
tags: ["Tag1"],
58+
progress: [SolutionStatus.IN_PROGRESS],
59+
};
60+
component.onModalFiltersApplied(modalFilters);
61+
expect(AllFiltersAppliedSpy).toHaveBeenCalledWith({
62+
...modalFilters,
63+
languages: component.languageFilters,
64+
});
65+
});
66+
it("should emit allFiltersApplied with correct filters when onLanguageFilterChange is called", () => {
67+
AllFiltersAppliedSpy = spyOn(component.allFiltersApplied, "emit");
68+
const languages = ["JavaScript", "Python"];
69+
component.onLanguageFilterChange(languages);
70+
expect(AllFiltersAppliedSpy).toHaveBeenCalledWith({
71+
...initialFilters,
72+
languages: component.languageFilters,
73+
});
74+
});
75+
it("should emit orderSelected with correct boolean value when changeOrder is called", () => {
76+
OrderSelectedSpy = spyOn(component.orderSelected, "emit");
77+
const isAscending = true;
78+
component.changeOrder(isAscending);
79+
expect(OrderSelectedSpy).toHaveBeenCalledWith(isAscending);
80+
});
81+
it("should emit sortSelected with correct sort value when changeSort is called", () => {
82+
SortSelectedSpy = spyOn(component.sortSelected, "emit");
83+
const sortValue = "difficulty";
84+
component.changeSort(sortValue);
85+
expect(SortSelectedSpy).toHaveBeenCalledWith(sortValue);
86+
});
87+
});

src/app/modules/starter/components/challenge-list-filters/challenge-list-filters.component.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ type ModalFilters = Pick<FilterChallenge, 'levels' | 'tags' | 'progress'>
1010
})
1111
export class ChallengeListFiltersComponent {
1212
@Input() initialFilters: FilterChallenge = { languages: [], levels: [], progress: [], tags: [] }
13-
@Output() filtersApplied = new EventEmitter<ModalFilters>()
13+
@Output() allFiltersApplied = new EventEmitter<FilterChallenge>()
1414
@Output() sortSelected = new EventEmitter<string>()
1515
@Output() orderSelected = new EventEmitter<boolean>()
1616
sortBy: string = 'popularity'
1717
isAscending: boolean = false
18+
modalFilters: ModalFilters = { levels: [], tags: [], progress: [] }
19+
languageFilters: string[] = []
1820

19-
protected onModalFiltersApplied (filters: ModalFilters): void {
20-
this.filtersApplied.emit(filters)
21+
onModalFiltersApplied (filters: ModalFilters): void {
22+
this.modalFilters = filters
23+
this.allFiltersApplied.emit({
24+
...filters,
25+
languages: this.languageFilters
26+
})
2127
}
2228

2329
changeSort (sort: string): void {
@@ -29,4 +35,12 @@ export class ChallengeListFiltersComponent {
2935
this.isAscending = isAscending
3036
this.orderSelected.emit(isAscending)
3137
}
38+
39+
onLanguageFilterChange (languages: string[]): void {
40+
this.languageFilters = languages
41+
this.allFiltersApplied.emit({
42+
...this.modalFilters,
43+
languages
44+
})
45+
}
3246
}

src/app/modules/starter/components/starter/starter.component.html

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,46 @@
11
<div class="starter">
22
<!-- OLD FILTERS COMPONENT -->
3-
<header class="challenges-header">
3+
<!-- <header class="challenges-header">
44
<div class="d-flex justify-content-between align-items-center challenges-filter-header">
55
<h2>{{ "modules.starter.main.section2.title" | translate }}</h2>
66
<div class="d-flex gap-2 align-items-center">
7-
<!-- MOBILE FILTER BUTTON -->
8-
<!-- TODO: Decide how this button fits with the new filters implementations,
9-
will the button be the same for the mobile and desktop versions? -->
7+
108
<button class="btn btn-outline-primary btnFiltrar d-xl-none" (click)="openModal()">
119
<strong>Filtrar</strong>
1210
</button>
13-
<!-- NEW CHALLENGE BUTTON -->
11+
1412
<button [routerLink]="['/ita-challenge/challenges/new-challenge']" *ngIf="isAdmin" class="btn btn-sm btn-primary new-challenge-btn">
1513
{{ "components.mainMenu.section4.title" | translate }}
1614
</button>
1715
</div>
1816
</div>
1917
<fieldset id="filters-container">
20-
<!-- SORT BY AND FILTERS SELECTOR -->
18+
2119
<div id="filters-selector">
22-
<!-- ORDER BY SELECTOR -->
23-
<!-- SORT BY -->
20+
2421
<app-sort-select
2522
[sortBy]="sortBy"
2623
[isAscending]="isAscending"
2724
(sortSelected)="changeSort($event)"
2825
(orderSelected)="changeOrder($event)">
2926
</app-sort-select>
30-
<!-- FILTERS SELECTOR -->
27+
3128
<div id="filters-panel" class="filters-panel open">
32-
<app-starter-filters (filtersSelected)="getChallengeFilters($event)"></app-starter-filters>
29+
<app-starter-filters ></app-starter-filters>
3330
</div>
3431
</div>
3532
</fieldset>
36-
</header>
33+
</header> -->
3734

3835

3936
<!-- NEW FILTERS COMPONENT -->
4037
<header id="challenge-filters">
4138
<app-challenge-list-filters
39+
[initialFilters]="filters"
40+
(allFiltersApplied)="getChallengeFilters($event)"
4241
(sortSelected)="changeSort($event)"
4342
(orderSelected)="changeOrder($event)">
44-
[initialFilters]="filters"
45-
(filtersApplied)="onModalFiltersApplied($event)">
43+
4644
</app-challenge-list-filters>
4745
</header>
4846

src/app/modules/starter/components/starter/starter.component.spec.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,7 @@ import { SolutionService } from 'src/app/services/solution.service';
1515
import { type UserSolution } from 'src/app/models/user-solution.interface';
1616

1717
describe('StarterComponent', () => {
18-
it('should render filters visible by default and without a toggle button', () => {
19-
const fixture = TestBed.createComponent(StarterComponent);
20-
fixture.detectChanges();
21-
const button: HTMLButtonElement | null = fixture.nativeElement.querySelector('#filters-toggle');
22-
const panel: HTMLElement | null = fixture.nativeElement.querySelector('#filters-panel');
23-
expect(button).toBeNull();
24-
expect(panel).toBeTruthy();
25-
// Panel should be open by default
26-
expect(panel?.classList.contains('open')).toBe(true);
27-
});
2818

29-
it('should have the filters panel present in the DOM', () => {
30-
const fixture = TestBed.createComponent(StarterComponent);
31-
fixture.detectChanges();
32-
const panel: HTMLElement | null = fixture.nativeElement.querySelector('#filters-panel');
33-
expect(panel).toBeTruthy();
34-
expect(panel?.classList.contains('open')).toBe(true);
35-
});
3619
it('should re-fetch challenges when refresh$ emits', () => {
3720
const fixture = TestBed.createComponent(StarterComponent);
3821
const component = fixture.componentInstance;

src/app/modules/starter/components/starter/starter.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ export class StarterComponent implements OnInit {
213213
this.solutionStatusMap = solutions.reduce<Record<string, SolutionStatus>>((statusMap, userSolution) => {
214214
statusMap[userSolution.uuid_challenge] = userSolution.status
215215
return statusMap
216-
}, {})
216+
}, {});
217+
this.cd.detectChanges();
217218
},
218219
error: (err) => {
219220
console.error('Error fetching user solutions:', err)

0 commit comments

Comments
 (0)