Build a Bookmark Manager App - Build a Bookmark Manager App

Tell us what’s happening:

Test 11 (adding a bookmark to local storage) is failing. The errors logged to the console don’t have enough information for me to tell what lines they’re referring to, so I’m relying on my local environment, where everything runs fine. I console log the local storage after adding to it, and everything’s in the right order. Similarly for 18, 19, 20, 22, 23 — everything seems to perform to requirements. Any guidance will be greatly appreciated.

Your code so far

/* file: script.js */
let bookmarks = [];
let selectedCategory = '';

const mainSection = document.getElementById("main-section");
const formSection = document.getElementById("form-section");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryList = document.getElementById("category-list");

const addBookmarkButton = document.getElementById("add-bookmark-button");
const closeFormButton = document.getElementById("close-form-button");
const addBookmarkFormButton = document.getElementById("add-bookmark-button-form")
const viewCategoryButton = document.getElementById("view-category-button");
const closeListButton = document.getElementById("close-list-button");
const deleteBookmarkButton = document.getElementById("delete-bookmark-button");

const getBookmarks = () => {
  const storedBookmarks = localStorage.getItem("bookmarks");
  if (storedBookmarks) {
    try {
      bookmarks = JSON.parse(storedBookmarks);
      if (!Array.isArray(bookmarks)) {
        bookmarks = [];
      } else {
          bookmarks = bookmarks.filter(item => item && typeof item === 'object' && 'name' in item && 'category' in item && 'url' in item);
      }
    } catch (error) {
      bookmarks = [];
    }
  }
  return bookmarks
}

const displayOrCloseForm = () => {
  mainSection.classList.toggle("hidden");
  formSection.classList.toggle("hidden");
}

const displayOrHideCategory = () => {
  mainSection.classList.toggle("hidden");
  bookmarkListSection.classList.toggle("hidden");
}

addBookmarkButton.addEventListener("click", () => {
  displayOrCloseForm();

  const chosenCategory = document.getElementById("category-dropdown");
  const selectedValue = chosenCategory.value;

  const categoryNames = document.querySelectorAll(".category-name");
  categoryNames.forEach((category) => category.innerText = selectedValue);
});

addBookmarkFormButton.addEventListener("click", () => {
  const nameInputEl = document.getElementById("name");
  const categoryInputEl = document.querySelector("h2.category-name");
  const urlInputEl = document.getElementById("url");

  const bookmark = {
    name: nameInputEl.value,
    category: categoryInputEl.innerText,
    url: urlInputEl.value
  }
  bookmarks.push(bookmark);
  localStorage.setItem("bookmarks", JSON.stringify(bookmarks))
  nameInputEl.value = '';
  urlInputEl.value = '';

  displayOrCloseForm()
})

viewCategoryButton.addEventListener("click", () => {
    const chosenCategory = document.getElementById("category-dropdown");
    selectedCategory = chosenCategory.value;
    viewCategoryList();
});

const viewCategoryList = () => {
    categoryList.innerHTML = ''; // Clear previous bookmarks
    const filteredBookmarks = bookmarks.filter(bookmark => bookmark.category === selectedCategory);
    
    if (filteredBookmarks.length > 0) {
        filteredBookmarks.forEach(bookmark => {
            const radioId = bookmark.name;
            categoryList.innerHTML += `
                <div>
                    <input type="radio" id="${radioId}" name="bookmark" value="${bookmark.name}" />
                    <label for="${radioId}">
                        <a href="${bookmark.url}" target="_blank">${bookmark.name}</a>
                    </label>
                </div>
            `;
        });
    } else {
        categoryList.innerHTML = "<p>No Bookmarks Found</p>";
    }
    if (bookmarkListSection.classList.contains("hidden")) {
        displayOrHideCategory();
    }
}

const deleteBookmark = () => {
    const selectedRadio = document.querySelector('input[name="bookmark"]:checked');
    
    if (!selectedRadio) {
        alert("Please select a bookmark to delete.");
        return;
    }

    const bookmarkToDelete = selectedRadio.value;
    const bookmark = bookmarks.find(b => b.name === bookmarkToDelete);
    if (bookmark) {
        selectedCategory = bookmark.category;
        bookmarks = bookmarks.filter(bookmark => bookmark.name !== bookmarkToDelete);
        localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
        alert(`${bookmarkToDelete} has been deleted.`);
        viewCategoryList();
    }
}

closeFormButton.addEventListener("click", displayOrCloseForm);
closeListButton.addEventListener("click", displayOrHideCategory);
deleteBookmarkButton.addEventListener("click", deleteBookmark);

document.addEventListener("DOMContentLoaded", () => {
    getBookmarks();
});
<!-- file: index.html —>
didn’t alter
/* file: styles.css */
didn’t alter

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.2 Safari/605.1.15 Ddg/26.2

Challenge Information:

Build a Bookmark Manager App - Build a Bookmark Manager App

[quote=“dhorowitz001, post:1, topic:774462”]
so I’m relying on my local environment, where everything runs fine.
[/quote]

Comparing to your local environment isn’t that useful because that environment does not run the same tests. The tests send garbage data to your function trying to break it intentionally.

It’s like saying your car works fine in your garage but then falls apart on the road when it hits a pothole.

Use console.log() to find out what input the tests are sending to your function to break it.

I take your point about the local environment. I added lots of console.log statements so I could check as I ran the program in the lab, and all my added bookmarks show up in localStorage in the right order.

Array (4)
0
{name: "example1", category: "news", url: "example1"}
1
{name: "example2", category: "news", url: "example2"}
2
{name: "example3", category: "news", url: "example3"}
3
{name: "example6", category: "news", url: "example6”}

But the tests report these results:

actual: Array (3)
0
{name: "test example5", category: "news", url: "test example5.com"}
1
{name: "test example2", category: "news", url: "test example2.com"}
2
{name: "test example3", category: "news", url: "test example3.com"}

Array Prototype

expected: Array (5)
0
{name: "example1", category: "news", url: "example1.com"}
1
{name: "example2", category: "entertainment", url: "example2.com"}
2
{name: "example3", category: "work", url: "example3.com"}
3
{name: "example4", category: "news", url: "example4.com"}
4
{name: "test example5", category: "news", url: "test example5.com”}

What else can I do to understand the test results?

the tests are updating the local storage before testing, it does not look like your app is retrieving from local storage during execution, so when the tests check the bookmarks, they don’t find what they expect

I’m adding checks after getBookmarks() runs, and trying other changes, but when I refresh the page to make sure localStorage.setItem works, my code reverts to the status before the checks – some labs have a ‘save code’ button but this one doesn’t – how can I ensure my edits are saved?

when you run the tests or use Ctrl+S your code is saved to local storage

1 Like

OK, I emptied localStorage via the app and tested that in the console after refreshing the page:

> console.log("Local storage: ", localStorage.getItem("bookmarks"))
[Log] Local storage:  – "[]"

Then I added 3 bookmarks. I can view them in the app, and in the console I get:

[Log] Updated bookmarks:  – [{name: “example1”, category: “news”, url: “example1”}] (1) (about:srcdoc, line 292)
[Log] Updated bookmarks:  – [{name: “example1”, category: “news”, url: “example1”}, {name: “example2”, category: “news”, url: “example2”}] (2) (about:srcdoc, line 292)
[Log] Updated bookmarks:  – [{name: “example1”, category: “news”, url: “example1”}, {name: “example2”, category: “news”, url: “example2”}, {name: “example3”, category: “news”, url: “example3”}] (3) (about:srcdoc, line 292)

The “Updated bookmarks” log is from the event listener on addBookmarkFormButton, and it’s logging a variable set to JSON.parse(localStorage.getItem("bookmarks”)).

Then I refresh the page, and view the bookmarks in the app, and test in the console:

[Log] Local storage: 
"[{\"name\":\"example1\",\"category\":\"news\",\"url\":\"example1\"},{\"name\":\"example2\",\"category\":\"news\",\"url\":\"example2\"},{\"name\":\"example3\",\"category\":\"news\",\"url\":\"example3\"}]”

but I get a reference error when trying to access the bookmarks array:

> console.log("Bookmarks array: ", bookmarks);
< ReferenceError: Can't find variable: bookmarks

I return bookmarks from getBookmarks(), and declare var bookmarks = at the top of the script. But I fail test 5, with the first error message in the console being:

[Error] n

actual: [{name: "example1", category: "news", notUrl: "example1.com"}, {name: "test example5", category: "news", url: "test example5.com"}] (2)

expected: undefined

message: "expected [ { name: 'example1', …(2) } ] to be empty"

operator: "strictEqual"

showDiff: false

stack: "@↵@↵eval code@↵eval@[native code]↵@https://www.freecodecamp.org/js/test-runner/7.2.0/dom-test-evaluator.js:2:25678…"

n Prototype

	(anonymous function) (dom-test-evaluator.js:2:256860)

So it empties when I run it, but not when the test runs? What might I look at next?

consider this:

  • the app loads
  • the test changes the local storage
  • then the test start clicking buttons

knowing this, can you move the point in the logic at which your app retrieve data from local storage?

I tried it 2 different ways, with getBookmarks() running when the event listener on DOMContentLoaded is triggered, and with getBookmarks() running right after it’s declared. When it runs right after it’s declared, I get this in the console:

[Log] Loaded bookmarks after getBookmarks() runs:  – [] (0) (about:srcdoc, line 251)
[Log] Local storage:  – null (about:srcdoc, line 252)
[Log] Loaded bookmarks after getBookmarks() runs:  – [] (0) (dom-test-evaluator.js, line 2)
[Log] Local storage:  – null (dom-test-evaluator.js, line 2)
[Log] Loaded bookmarks after getBookmarks() runs:  (dom-test-evaluator.js, line 2)
Array (4)
0
{name: "example1", category: "news", url: "example1.com"}
1
{name: "example2", category: "entertainment", url: "example2.com"}
2
{name: "example3", category: "work", url: "example3.com"}
3
{name: "example4", category: "news", url: "example4.com"}

Array Prototype
[Log] Local storage:  – null (dom-test-evaluator.js, line 2)```

The log statements (Local storage: and Loaded bookmarks after getBookmarks() runs) suggest that getBookmarks() is being called twice — and it’s empty twice. How can there be 4 items in localStorage on the 3rd run when the test didn’t add any items ? I have log statements after a bookmark is pushed to bookmarks and they’re not in the console.

When it runs after DOMContentLoaded, the console logs indicate that getBookmarks() runs once when triggered by DOMContentLoaded (the single ‘JSON parsed bookmarks’ log while localStorage is empty, and then 2 more times before loading anything from localStorage — is the test accessing localStorage directly?

[Log] Loaded bookmarks after getBookmarks() runs:  – [] (0) (about:srcdoc, line 251)
[Log] Local storage:  – null (about:srcdoc, line 252)
[Log] JSON parsed 'bookmarks':  – null (about:srcdoc, line 372)
[Log] Loaded bookmarks after getBookmarks() runs:  – [] (0) (dom-test-evaluator.js, line 2)
[Log] Local storage:  – null (dom-test-evaluator.js, line 2)
[Log] Loaded bookmarks after getBookmarks() runs:  (dom-test-evaluator.js, line 2)
Array (4)
0
{name: "example1", category: "news", url: "example1.com"}
1
{name: "example2", category: "entertainment", url: "example2.com"}
2
{name: "example3", category: "work", url: "example3.com"}
3
{name: "example4", category: "news", url: "example4.com"}

Array Prototype
[Log] Local storage:  – null (dom-test-evaluator.js, line 2)

each test starts by resetting localStorage to a known state

yes, the test is updating localStorage so it can see what your app is doing with a known set of bookmarks

If you want more specific suggestions, share your code

Here’s my code. I still don’t understand how it could load the 4 bookmarks from localStorage but then immediately afterwards show local storage as null.

var bookmarks = [];
const expectedProperties = ["name", "category", "url"];

let selectedCategory = '';

const mainSection = document.getElementById("main-section");
const formSection = document.getElementById("form-section");
const bookmarkListSection = document.getElementById("bookmark-list-section");
const categoryList = document.getElementById("category-list");

const addBookmarkButton = document.getElementById("add-bookmark-button");
const closeFormButton = document.getElementById("close-form-button");
const addBookmarkFormButton = document.getElementById("add-bookmark-button-form")
const viewCategoryButton = document.getElementById("view-category-button");
const closeListButton = document.getElementById("close-list-button");
const deleteBookmarkButton = document.getElementById("delete-bookmark-button");

const isValidBookmark = (bookmark) => {
  if (typeof bookmark === 'object' && bookmark !== null) {
    return expectedProperties.every(prop => prop in bookmark && typeof bookmark[prop] === 'string');
  }
  return false;
};

const getBookmarks = () => {
  console.log("Bookmarks before getBookmarks() runs: ", bookmarks)
  const storedBookmarks = localStorage.getItem("bookmarks");
  if (storedBookmarks) {
    try {
      const parsedBookmarks = JSON.parse(storedBookmarks);
      if (Array.isArray(parsedBookmarks)) {
        const validBookmarks = parsedBookmarks.filter(isValidBookmark);
        bookmarks = validBookmarks;
      } else {
          bookmarks = [];
      }
    } catch (error) {
        bookmarks = [];
    }
  } else {
      bookmarks = [];
  }
  console.log("Loaded bookmarks after getBookmarks() runs: ", bookmarks);
  console.log("Local storage: ", localStorage.getItem("data"));
  return bookmarks;
};

//getBookmarks();

const displayOrCloseForm = () => {
  mainSection.classList.toggle("hidden");
  formSection.classList.toggle("hidden");
}

const displayOrHideCategory = () => {
  mainSection.classList.toggle("hidden");
  bookmarkListSection.classList.toggle("hidden");
}

addBookmarkButton.addEventListener("click", () => {
  displayOrCloseForm();

  const chosenCategory = document.getElementById("category-dropdown");
  const selectedValue = chosenCategory.value;

  const categoryNames = document.querySelectorAll(".category-name");
  categoryNames.forEach((category) => category.innerText = selectedValue);
});

addBookmarkFormButton.addEventListener("click", () => {
  const nameInputEl = document.getElementById("name");
  const categoryInputEl = document.querySelector("h2.category-name");
  const urlInputEl = document.getElementById("url");

  const bookmark = {
    name: nameInputEl.value,
    category: categoryInputEl.innerText,
    url: urlInputEl.value
  }
  bookmarks.push(bookmark);
  console.log("Bookmark pushed: ", bookmark);
  console.log("Bookmarks array after push: ", bookmarks);
  localStorage.setItem("bookmarks", JSON.stringify(bookmarks))
  const savedBookmarks = JSON.parse(localStorage.getItem("bookmarks"));
  console.log("Updated bookmarks: ", savedBookmarks);
  nameInputEl.value = '';
  urlInputEl.value = '';

  displayOrCloseForm()
})

viewCategoryButton.addEventListener("click", () => {
    const chosenCategory = document.getElementById("category-dropdown");
    selectedCategory = chosenCategory.value;
    viewCategoryList();
});

const viewCategoryList = () => {
    categoryList.innerHTML = ''; // Clear previous bookmarks
    const filteredBookmarks = bookmarks.filter(bookmark => bookmark.category === selectedCategory);
    
    if (filteredBookmarks.length > 0) {
        filteredBookmarks.forEach(bookmark => {
            const radioId = bookmark.name;
            categoryList.innerHTML += `
                <div>
                    <input type="radio" id="${radioId}" name="bookmark" value="${bookmark.name}" />
                    <label for="${radioId}">
                        <a href="${bookmark.url}" target="_blank">${bookmark.name}</a>
                    </label>
                </div>
            `;
        });
    } else {
        categoryList.innerHTML = "<p>No Bookmarks Found</p>";
    }
    if (bookmarkListSection.classList.contains("hidden")) {
        displayOrHideCategory();
    }
}

const deleteBookmark = () => {
    const selectedRadio = document.querySelector('input[name="bookmark"]:checked');
    
    if (!selectedRadio) {
        alert("Please select a bookmark to delete.");
        return;
    }

    const bookmarkToDelete = selectedRadio.value;
    const bookmark = bookmarks.find(b => b.name === bookmarkToDelete);
    if (bookmark) {
        selectedCategory = bookmark.category;
        bookmarks = bookmarks.filter(bookmark => bookmark.name !== bookmarkToDelete);
        localStorage.setItem("bookmarks", JSON.stringify(bookmarks));
        alert(`${bookmarkToDelete} has been deleted.`);
        viewCategoryList();
    }
}

closeFormButton.addEventListener("click", displayOrCloseForm);
closeListButton.addEventListener("click", displayOrHideCategory);
deleteBookmarkButton.addEventListener("click", deleteBookmark);

document.addEventListener("DOMContentLoaded", () => {
    getBookmarks();
    console.log("JSON parsed 'bookmarks': ", JSON.parse(localStorage.getItem("bookmarks")))
});

do you expect data to have a value? why are you checking this and not bookmarks?

other issue, you use

you use getBookmarks only here, but this will not work, because the app loads, then the tests update the local storage, and you never get the updated local storage.
Try linking getting bookmarks from localstorage to clicking on a button or something like that

Thanks – I got it. Guess I need more experience in thinking through how to test! I didn’t pay enough attention to it when you said the test changes local storage before clicking buttons.