diff --git a/apps/okreads-e2e/src/integration/reading-list.spec.ts b/apps/okreads-e2e/src/integration/reading-list.spec.ts
index dd8668a..289d770 100644
--- a/apps/okreads-e2e/src/integration/reading-list.spec.ts
+++ b/apps/okreads-e2e/src/integration/reading-list.spec.ts
@@ -11,4 +11,34 @@ describe('When: I use the reading list feature', () => {
'My Reading List'
);
});
+
+ it('Then: I should add book back to reading list which was removed, on undo snackbar action', () => {
+ cy.get('tmo-root').should('contain.text', 'okreads');
+
+ cy.get('#searchInput').type('python');
+ cy.get('form').submit();
+
+ cy.get('[data-testing="book-item"]')
+ .find('button:not(:disabled)')
+ .its('length')
+ .should('be.gt', 0)
+ .then(() => {
+ cy.get('button[id^="wantToRead-"]:not(:disabled)').first().click();
+
+ cy.get('[data-testing="toggle-reading-list"]').click();
+ cy.get('[data-testing="reading-list-container"]')
+ .should(
+ 'contain.text',
+ 'My Reading List'
+ );
+
+ cy.get('button[id^="btnRemove-"]').first().click();
+ cy.get('.mat-simple-snackbar-action .mat-button').last().click();
+
+ cy.get('[data-testing="reading-list-container"]')
+ .find('.reading-list-item')
+ .its('length')
+ .should('be.gt', 0)
+ });
+ });
});
diff --git a/apps/okreads/browser/src/app/app.component.html b/apps/okreads/browser/src/app/app.component.html
index 31e82c9..6c67134 100644
--- a/apps/okreads/browser/src/app/app.component.html
+++ b/apps/okreads/browser/src/app/app.component.html
@@ -7,8 +7,7 @@
My Reading List
-
diff --git a/libs/books/feature/src/lib/book-search/book-search.component.spec.ts b/libs/books/feature/src/lib/book-search/book-search.component.spec.ts
index 292a608..606141e 100644
--- a/libs/books/feature/src/lib/book-search/book-search.component.spec.ts
+++ b/libs/books/feature/src/lib/book-search/book-search.component.spec.ts
@@ -12,6 +12,7 @@ import {
getBooksError,
getBooksLoaded,
searchBooks,
+ removeFromReadingList,
} from '@tmo/books/data-access';
import { BooksFeatureModule } from '../books-feature.module';
@@ -23,6 +24,8 @@ Object.defineProperty(window, 'matchMedia', {
matches: false,
media: query,
onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
@@ -166,4 +169,30 @@ describe('BookSearch Component', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(clearSearch());
});
+
+ it('should remove book from readinglist on undo action of snackbar', () => {
+ const bookToRead = { ...createBook('9U5I_tskq9MC'), isAdded: false };
+
+ store.overrideSelector(getAllBooks, [
+ { ...bookToRead },
+ { ...createBook('qU3rAgAAQBAJ'), isAdded: false, publishedDate: null },
+ { ...createBook('PXa2bby0oQ0C'), isAdded: false }
+ ]);
+
+ const searchCtrl = fixture.debugElement.query(By.css('#searchInput'));
+ searchCtrl.nativeElement.value = 'javascript';
+ searchCtrl.nativeElement.dispatchEvent(new Event('input'));
+ store.refreshState();
+ fixture.detectChanges();
+
+ const btnWantToRead = fixture.debugElement.query(By.css('#wantToRead-9U5I_tskq9MC'));
+ btnWantToRead.nativeElement.click();
+
+ const btnUndoAddToReadingList = (
document.querySelector('.mat-simple-snackbar-action .mat-button'));
+ btnUndoAddToReadingList.click();
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ removeFromReadingList({ item: { bookId: bookToRead.id, ...bookToRead } })
+ );
+ });
});
diff --git a/libs/books/feature/src/lib/book-search/book-search.component.ts b/libs/books/feature/src/lib/book-search/book-search.component.ts
index 0605ecf..101a4fa 100644
--- a/libs/books/feature/src/lib/book-search/book-search.component.ts
+++ b/libs/books/feature/src/lib/book-search/book-search.component.ts
@@ -1,7 +1,10 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
+import { MatSnackBar } from '@angular/material/snack-bar';
+
import { Observable } from 'rxjs';
+import { take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
@@ -12,9 +15,10 @@ import {
ReadingListBook,
searchBooks,
getBooksError,
- getBooksLoaded
+ getBooksLoaded,
+ removeFromReadingList
} from '@tmo/books/data-access';
-import { Book } from '@tmo/shared/models';
+import { Book, ReadingListItem } from '@tmo/shared/models';
@Component({
selector: 'tmo-book-search',
@@ -36,8 +40,9 @@ export class BookSearchComponent {
constructor(
private readonly store: Store,
- private readonly fb: FormBuilder
- ) {}
+ private readonly fb: FormBuilder,
+ private readonly snackBar: MatSnackBar
+ ) { }
get searchTerm(): string {
return this.searchForm.value.term;
@@ -51,6 +56,21 @@ export class BookSearchComponent {
addBookToReadingList(book: Book) {
this.store.dispatch(addToReadingList({ book }));
+
+ const snackBarUndoAdd = this.snackBar.open(
+ `${book.title} - is added to your reading list`,
+ 'Undo',
+ { duration: 10000 }
+ );
+ snackBarUndoAdd.onAction().pipe(take(1)).subscribe(() => {
+ const item: ReadingListItem = {
+ ...book,
+ bookId: book.id
+ };
+
+ this.store.dispatch(removeFromReadingList({ item }));
+ this.snackBar.dismiss();
+ });
}
searchExample() {
diff --git a/libs/books/feature/src/lib/books-feature.module.ts b/libs/books/feature/src/lib/books-feature.module.ts
index 513e02e..8649cbb 100644
--- a/libs/books/feature/src/lib/books-feature.module.ts
+++ b/libs/books/feature/src/lib/books-feature.module.ts
@@ -11,6 +11,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon';
import { MatBadgeModule } from '@angular/material/badge';
import { MatSnackBarModule } from '@angular/material/snack-bar';
+
import { BooksDataAccessModule } from '@tmo/books/data-access';
import { BookSearchComponent } from './book-search/book-search.component';
diff --git a/libs/books/feature/src/lib/reading-list/reading-list.component.spec.ts b/libs/books/feature/src/lib/reading-list/reading-list.component.spec.ts
index 20cd221..81ba61c 100644
--- a/libs/books/feature/src/lib/reading-list/reading-list.component.spec.ts
+++ b/libs/books/feature/src/lib/reading-list/reading-list.component.spec.ts
@@ -1,12 +1,12 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { createReadingListItem, SharedTestingModule } from '@tmo/shared/testing';
import { BooksFeatureModule } from '@tmo/books/feature';
-import { getReadingList, removeFromReadingList } from '@tmo/books/data-access';
-
+import { addToReadingList, getReadingList, removeFromReadingList } from '@tmo/books/data-access';
import { ReadingListComponent } from './reading-list.component';
describe('ReadingList Component', () => {
@@ -16,7 +16,7 @@ describe('ReadingList Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [BooksFeatureModule, SharedTestingModule],
+ imports: [BooksFeatureModule, SharedTestingModule, NoopAnimationsModule],
providers: [provideMockStore()]
}).compileComponents();
}));
@@ -46,4 +46,18 @@ describe('ReadingList Component', () => {
expect(store.dispatch).toHaveBeenCalledWith(removeFromReadingList({ item: readingListItem }));
});
+
+ it('should add book back to readinglist on undo snackbar action', () => {
+ const readingListItem = createReadingListItem('9U5I_tskq9MC');
+ fixture.detectChanges();
+ const btnRemove = fixture.debugElement.query(By.css('#btnRemove-9U5I_tskq9MC'));
+ btnRemove.nativeElement.click();
+
+ const btnUndoRemoveFromList = (document.querySelector('.mat-simple-snackbar-action .mat-button'));
+ btnUndoRemoveFromList.click();
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ addToReadingList({ book: { id: readingListItem.bookId, ...readingListItem } })
+ );
+ });
});
diff --git a/libs/books/feature/src/lib/reading-list/reading-list.component.ts b/libs/books/feature/src/lib/reading-list/reading-list.component.ts
index 0174904..7c3135e 100644
--- a/libs/books/feature/src/lib/reading-list/reading-list.component.ts
+++ b/libs/books/feature/src/lib/reading-list/reading-list.component.ts
@@ -1,11 +1,18 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Observable } from 'rxjs';
+import { take } from 'rxjs/operators';
+
+import { MatSnackBar } from '@angular/material/snack-bar';
import { Store } from '@ngrx/store';
-import { getReadingList, removeFromReadingList } from '@tmo/books/data-access';
-import { ReadingListItem } from '@tmo/shared/models';
+import {
+ getReadingList,
+ removeFromReadingList,
+ addToReadingList
+} from '@tmo/books/data-access';
+import { Book, ReadingListItem } from '@tmo/shared/models';
@Component({
selector: 'tmo-reading-list',
@@ -16,9 +23,28 @@ import { ReadingListItem } from '@tmo/shared/models';
export class ReadingListComponent {
readingList$: Observable = this.store.select(getReadingList);
- constructor(private readonly store: Store) { }
+ constructor(
+ private readonly store: Store,
+ private readonly snackBar: MatSnackBar
+ ) { }
removeFromReadingList(item: ReadingListItem) {
this.store.dispatch(removeFromReadingList({ item }));
+
+ const snackBarUndoRemove = this.snackBar.open(
+ `${item.title} - is removed from your reading list`,
+ 'Undo',
+ { duration: 10000 }
+ );
+
+ snackBarUndoRemove.onAction().pipe(take(1)).subscribe(() => {
+ const book: Book = {
+ ...item,
+ id: item.bookId
+ };
+
+ this.store.dispatch(addToReadingList({ book }));
+ this.snackBar.dismiss();
+ });
}
}
diff --git a/libs/shared/styles/src/lib/base.scss b/libs/shared/styles/src/lib/base.scss
index 64a86db..db9e348 100644
--- a/libs/shared/styles/src/lib/base.scss
+++ b/libs/shared/styles/src/lib/base.scss
@@ -30,3 +30,13 @@ a {
color: $pink-dark;
}
}
+
+.mat-simple-snackbar span {
+ color:#ffffff;
+}
+.mat-simple-snackbar-action .mat-button span {
+ color:$pink-accent;
+}
+.mat-snack-bar-container {
+ background-color: #171b14;
+}
\ No newline at end of file