diff --git a/curriculum/challenges/english/25-front-end-development/lab-bookmark-manager-app/66def5467aee701733aaf8cc.md b/curriculum/challenges/english/25-front-end-development/lab-bookmark-manager-app/66def5467aee701733aaf8cc.md index 0a32305ce2f..8de355f8975 100644 --- a/curriculum/challenges/english/25-front-end-development/lab-bookmark-manager-app/66def5467aee701733aaf8cc.md +++ b/curriculum/challenges/english/25-front-end-development/lab-bookmark-manager-app/66def5467aee701733aaf8cc.md @@ -14,7 +14,7 @@ Fulfill the user stories below and get all the tests to pass to complete the lab **User Stories:** -1. You should have a `getBookmarks` function that returns the `bookmarks` array stored in the local storage. If the `bookmarks` key has not been set yet, the `getBookmarks` function should return an empty array. +1. You should have a `getBookmarks` function that returns the `bookmarks` array stored in the local storage. If the `bookmarks` key has not been set yet, or it doesn't contain a valid array with bookmarks, the `getBookmarks` function should return an empty array. 1. The `bookmarks` key stored in the local storage should be an array of bookmark objects. Each bookmark object should have three keys: `name`, `category`, and `url`. 1. You should have a function named `displayOrCloseForm` that toggles the `hidden` class on `#main-section` and `#form-section`. 1. When you click `#add-bookmark-button`, you should update the inner text of `.category-name` to be the value of the selected option from `#category-dropdown` and call `displayOrCloseForm` to display the form section and hide the main section. @@ -24,10 +24,10 @@ Fulfill the user stories below and get all the tests to pass to complete the lab 1. You should have a function named `displayOrHideCategory` that toggles the `hidden` class on `#main-section` and `#bookmark-list-section`. 1. When you click `#view-category-button`, you should update the inner text of `.category-name` to be the value of the selected option from `#category-dropdown`, modify the inner HTML of `#category-list` according to the user stories below, and call the `displayOrHideCategory` function. 1. If none of the bookmarks in local storage have the category, you should set the inner HTML of the `#category-list` to a `p` element with the text `No Bookmarks Found`. -1. If one or more bookmarks in local storage have the selected category you should add a radio button with `id` and `value` attributes set to the bookmark name to the `#category-list`'s inner HTML for each of those bookmarks. +1. If one or more bookmarks in local storage have the selected category, you should add a radio button with `id` and `value` attributes, and set to the bookmark name to the `#category-list`'s inner HTML for each of those bookmarks. Additionally, each radio button should have the same `name` attribute. 1. Each radio button should have a corresponding label containing an anchor element with the bookmark name and the `href` attribute set to the bookmark URL. -1. When you click `#close-list-button`, you should run your function to hide the `#bookmark-list-section` and display the main section. -1. When you click `#delete-bookmark-button`, you should delete the bookmark corresponding to the selected radio button from the local storage and update the displayed bookmark list. +1. When you click the `#close-list-button`, you should run your function to hide the `#bookmark-list-section` and display the main section. +1. When you click the `#delete-bookmark-button`, you should delete the bookmark corresponding to the selected radio button and appropriate category from the local storage and update the displayed bookmark list. # --hints-- @@ -69,6 +69,24 @@ try { } ``` +When the `bookmarks` key in the `localStorage` does not contain a valid array of bookmark objects, the `getBookmarks` function should return an empty array. + +```js +try { + localStorage.setItem("bookmarks", 'invalid'); + const arrayFromInvalidValue = getBookmarks(); + assert.isArray(arrayFromInvalidValue); + assert.isEmpty(arrayFromInvalidValue); + + localStorage.setItem("bookmarks", JSON.stringify([{name: "example1", category: "news", notUrl: "example1.com"}])); + const arrayFromInvalidArray = getBookmarks(); + assert.isArray(arrayFromInvalidArray) + assert.isEmpty(arrayFromInvalidArray) +} finally { + resetLocalStorage(); +} +``` + You should have a function named `displayOrCloseForm`. ```js @@ -201,7 +219,7 @@ try { } ``` -When you click `#view-category-button`, you should modify `#category-list`'s inner HTML by adding a radio button having the `id` and `value` attributes set to the bookmark name for each bookmark in the selected category. +When you click the `#view-category-button`, you should modify the `#category-list` element's inner HTML by adding a radio button. The radio button should have the `id` and `value` attributes set to the bookmark name for each bookmark in the selected category. Additionally, each radio button should have the same `name` attribute. ```js try { @@ -214,6 +232,8 @@ try { assert.strictEqual(bookmarksDisplayed[0].value, "example1"); assert.strictEqual(bookmarksDisplayed[1].id, "example4"); assert.strictEqual(bookmarksDisplayed[1].value, "example4"); + assert.isOk(bookmarksDisplayed[0].name) + assert.strictEqual(bookmarksDisplayed[0].name, bookmarksDisplayed[1].name); } finally { resetLocalStorage(); } @@ -262,7 +282,49 @@ closeListButtonTest.dispatchEvent(new Event("click")); assert.strictEqual(getHidden("bookmark list"), "bookmark list"); ``` -When you click `#delete-bookmark-button`, you should delete the bookmark corresponding to the selected radio button from the local storage and update the displayed bookmark list. +When you click the `#close-list-button` and then open any category, the `#category-list` should contain only data relevant for the selected category, without duplicating entries. + +```js +setLocalStorage(); +try { + categoryDropdownTest.value = "miscellaneous"; + viewCategoryButtonTest.dispatchEvent(new Event("click")); + const categoryList = document.getElementById("category-list").innerHTML; + assert.strictEqual(categoryList, "
No Bookmarks Found
"); + + closeListButtonTest.dispatchEvent(new Event("click")); + categoryDropdownTest.value = "news"; + viewCategoryButtonTest.dispatchEvent(new Event("click")); + + const newsBookmarks = document.querySelectorAll('#category-list input[type="radio"]'); + assert.lengthOf(newsBookmarks, 2); + assert.strictEqual(newsBookmarks[0].id, "example1"); + assert.strictEqual(newsBookmarks[0].value, "example1"); + assert.strictEqual(newsBookmarks[1].id, "example4"); + assert.strictEqual(newsBookmarks[1].value, "example4"); + + closeListButtonTest.dispatchEvent(new Event("click")); + categoryDropdownTest.value = "work"; + viewCategoryButtonTest.dispatchEvent(new Event("click")); + + const workBookmarks = document.querySelectorAll('#category-list input[type="radio"]'); + assert.lengthOf(workBookmarks, 1); + assert.strictEqual(workBookmarks[0].id, "example3"); + assert.strictEqual(workBookmarks[0].value, "example3"); + + categoryDropdownTest.value = "miscellaneous"; + viewCategoryButtonTest.dispatchEvent(new Event("click")); + + const paragraphs = document.querySelectorAll("#category-list p"); + assert.lengthOf(paragraphs, 1); + assert.strictEqual(paragraphs[0].innerText, "No Bookmarks Found"); +} finally { + resetLocalStorage(); + clearCategoryList(); +} +``` + +When you click the `#delete-bookmark-button`, you should delete the bookmark corresponding to the selected radio button and appropriate category from the local storage and update the displayed bookmark list. ```js setLocalStorage(); @@ -281,7 +343,17 @@ try { assert.strictEqual(bookmarkLabelsDisplayed[0].htmlFor, "example4"); const expected = [{name: "example2", category: "entertainment", url: "example2.com"}, {name: "example3", category: "work", url: "example3.com"}, {name: "example4", category: "news", url: "example4.com"}]; assert.deepEqual(getBookmarks(), expected); - + + localStorage.setItem("bookmarks", JSON.stringify([{name: "duplicated-name", category: "news", url: "example1.com"}, {name: "duplicated-name", category: "entertainment", url: "example2.com"}])); + + categoryDropdownTest.value = "entertainment"; + viewCategoryButtonTest.dispatchEvent(new Event("click")); + const radioForDuplicate = document.querySelector('#category-list input[type="radio"]'); + radioForDuplicate.checked = true; + + deleteBookmarkButtonTest.dispatchEvent(new Event("click")); + + assert.deepEqual(getBookmarks(), [{name: "duplicated-name", category: "news", url: "example1.com"}]) } finally { resetLocalStorage(); } @@ -529,7 +601,26 @@ const addBookmarkButtonForm = document.getElementById("add-bookmark-button-form" const categoryList = document.getElementById("category-list"); const closeListButton = document.getElementById("close-list-button"); const deleteBookmarkButton = document.getElementById("delete-bookmark-button"); -const getBookmarks = () => JSON.parse(localStorage.getItem("bookmarks")) || []; + +const getBookmarks = () => { + try { + const parsed = JSON.parse(localStorage.getItem("bookmarks")); + if ( + Array.isArray(parsed) && + parsed.every( + (item) => + item.hasOwnProperty("category") && + item.hasOwnProperty("name") && + item.hasOwnProperty("url"), + ) + ) { + return parsed; + } + return []; + } catch { + return []; + } +}; const displayOrCloseForm = () => { mainSection.classList.toggle("hidden"); @@ -570,12 +661,7 @@ const displayOrHideCategory = () => { const fillBookmarkList = () => { categoryHeading[1].innerText = categoryDropdown.value.charAt(0).toUpperCase() + categoryDropdown.value.slice(1); - const bookmarksToDisplay = getBookmarks().filter((i) => { - if (i.category === categoryDropdown.value) { - return true - } - return false - }); + const bookmarksToDisplay = getBookmarks().filter((i) => i.category === categoryDropdown.value); if (bookmarksToDisplay.length) { categoryList.innerHTML = ''; for (const bookmark of bookmarksToDisplay) { @@ -604,7 +690,7 @@ const deleteBookmark = () => { for (const radioBookmark of radioBookmarks) { if (radioBookmark.checked) { const bookmarks = getBookmarks(); - const indexToRemove = bookmarks.findIndex(i => i.name == radioBookmark.value); + const indexToRemove = bookmarks.findIndex(i => i.name == radioBookmark.value && i.category === categoryDropdown.value); bookmarks.splice(indexToRemove, 1); localStorage.setItem("bookmarks", JSON.stringify(bookmarks)); return