|
@@ -13,7 +13,15 @@
|
|
| 13 |
<section>
|
| 14 |
<h1>Books</h1>
|
| 15 |
<div>
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
<app-book-card [book]="b" (like)="addLikedBook($event)" />
|
| 18 |
}
|
| 19 |
</div>
|
|
|
|
| 13 |
<section>
|
| 14 |
<h1>Books</h1>
|
| 15 |
<div>
|
| 16 |
+
<input
|
| 17 |
+
type="search"
|
| 18 |
+
[value]="searchTerm()"
|
| 19 |
+
(input)="searchTerm.set($event.target.value)"
|
| 20 |
+
placeholder="Search"
|
| 21 |
+
aria-label="Search"
|
| 22 |
+
/>
|
| 23 |
+
|
| 24 |
+
@for (b of filteredBooks(); track b.isbn) {
|
| 25 |
<app-book-card [book]="b" (like)="addLikedBook($event)" />
|
| 26 |
}
|
| 27 |
</div>
|
|
@@ -51,4 +51,34 @@ describe('BooksOverviewPage', () => {
|
|
| 51 |
expect(bookCardEls[0].textContent).toContain('Tierisch gut kochen');
|
| 52 |
expect(bookCardEls[1].textContent).toContain('Backen mit Affen');
|
| 53 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
});
|
|
|
|
| 51 |
expect(bookCardEls[0].textContent).toContain('Tierisch gut kochen');
|
| 52 |
expect(bookCardEls[1].textContent).toContain('Backen mit Affen');
|
| 53 |
});
|
| 54 |
+
|
| 55 |
+
it('should display all books if the search term is empty', () => {
|
| 56 |
+
component['searchTerm'].set('');
|
| 57 |
+
|
| 58 |
+
const books = component['filteredBooks']();
|
| 59 |
+
expect(books.length).toBe(2);
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
it('should filter books based on the search term', () => {
|
| 63 |
+
component['searchTerm'].set('Affe');
|
| 64 |
+
|
| 65 |
+
const books = component['filteredBooks']();
|
| 66 |
+
expect(books.length).toBe(1);
|
| 67 |
+
expect(books[0].title).toBe('Backen mit Affen');
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
it('should filter books ignoring case sensitivity', () => {
|
| 71 |
+
component['searchTerm'].set('AFFEN');
|
| 72 |
+
|
| 73 |
+
const books = component['filteredBooks']();
|
| 74 |
+
expect(books.length).toBe(1);
|
| 75 |
+
expect(books[0].title).toBe('Backen mit Affen');
|
| 76 |
+
});
|
| 77 |
+
|
| 78 |
+
it('should return an empty array if no book matches the search term', () => {
|
| 79 |
+
component['searchTerm'].set('unbekannter Titel');
|
| 80 |
+
|
| 81 |
+
const books = component['filteredBooks']();
|
| 82 |
+
expect(books.length).toBe(0);
|
| 83 |
+
});
|
| 84 |
});
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { Component, inject, signal } from '@angular/core';
|
| 2 |
|
| 3 |
import { Book } from '../../shared/book';
|
| 4 |
import { BookCard } from '../book-card/book-card';
|
|
@@ -13,9 +13,20 @@ import { BookStore } from '../../shared/book-store';
|
|
| 13 |
export class BooksOverviewPage {
|
| 14 |
#bookStore = inject(BookStore);
|
| 15 |
|
|
|
|
|
|
|
| 16 |
protected books = signal<Book[]>([]);
|
| 17 |
protected likedBooks = signal<Book[]>([]);
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
constructor() {
|
| 20 |
this.books.set(this.#bookStore.getAll());
|
| 21 |
}
|
|
|
|
| 1 |
+
import { Component, computed, inject, signal } from '@angular/core';
|
| 2 |
|
| 3 |
import { Book } from '../../shared/book';
|
| 4 |
import { BookCard } from '../book-card/book-card';
|
|
|
|
| 13 |
export class BooksOverviewPage {
|
| 14 |
#bookStore = inject(BookStore);
|
| 15 |
|
| 16 |
+
protected searchTerm = signal('');
|
| 17 |
+
|
| 18 |
protected books = signal<Book[]>([]);
|
| 19 |
protected likedBooks = signal<Book[]>([]);
|
| 20 |
|
| 21 |
+
protected filteredBooks = computed(() => {
|
| 22 |
+
if (!this.searchTerm()) {
|
| 23 |
+
return this.books();
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
const term = this.searchTerm().toLowerCase();
|
| 27 |
+
return this.books().filter((b) => b.title.toLowerCase().includes(term));
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
constructor() {
|
| 31 |
this.books.set(this.#bookStore.getAll());
|
| 32 |
}
|