diff --git a/Sprint-3/package.json b/Sprint-3/package.json index 711a5390f..722b1d55d 100644 --- a/Sprint-3/package.json +++ b/Sprint-3/package.json @@ -4,7 +4,7 @@ "license": "CC-BY-SA-4.0", "description": "", "scripts": { - "test": "jest", + "test": "jest --config=../jest.config.js reading-list", "format": "prettier --write ." }, "workspaces": [ @@ -26,7 +26,7 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", - "jest": "^30.0.4", + "jest": "^30.2.0", "jest-environment-jsdom": "^30.0.4" } } diff --git a/Sprint-3/reading-list/index.html b/Sprint-3/reading-list/index.html index dbdb0f471..d1d016f3b 100644 --- a/Sprint-3/reading-list/index.html +++ b/Sprint-3/reading-list/index.html @@ -1,14 +1,23 @@ - + - Title here + Reading list app
- +

My Reading List

+
+ + Already Read + + + To Read + +
+
    diff --git a/Sprint-3/reading-list/package.json b/Sprint-3/reading-list/package.json index 96f540518..827682171 100644 --- a/Sprint-3/reading-list/package.json +++ b/Sprint-3/reading-list/package.json @@ -4,7 +4,7 @@ "license": "CC-BY-SA-4.0", "description": "You must update this package", "scripts": { - "test": "jest --config=../jest.config.js reading-list" + "test": "jest" }, "repository": { "type": "git", @@ -13,5 +13,8 @@ "bugs": { "url": "https://github.com/CodeYourFuture/CYF-Coursework-Template/issues" }, - "homepage": "https://github.com/CodeYourFuture/CYF-Coursework-Template#readme" + "homepage": "https://github.com/CodeYourFuture/CYF-Coursework-Template#readme", + "devDependencies": { + "@testing-library/jest-dom": "^6.9.1" + } } diff --git a/Sprint-3/reading-list/script.js b/Sprint-3/reading-list/script.js index 6024d73a0..7d201c3f5 100644 --- a/Sprint-3/reading-list/script.js +++ b/Sprint-3/reading-list/script.js @@ -21,3 +21,46 @@ const books = [ }, ]; +function readingList() { + const orderedList = document.querySelector("#reading-list"); + + // Base condition: check if books array is empty + if (!books || books.length === 0) { + const emptyMessage = document.createElement("li"); + emptyMessage.className = "empty-message"; + emptyMessage.textContent = + "No books in your reading list yet. Add some books to get started!"; + orderedList.appendChild(emptyMessage); + return; + } + + for (const book of books) { + const listItem = document.createElement("li"); + + const bookImage = document.createElement("img"); + bookImage.src = book.bookCoverImage; + bookImage.alt = "Book cover for " + book.title; + + const bookInfo = document.createElement("p"); + bookInfo.textContent = book.title + " by " + book.author; + + const statusBadge = document.createElement("span"); + statusBadge.className = "status-badge"; + + if (book.alreadyRead) { + listItem.classList.add("read"); + statusBadge.textContent = "✓"; + } else { + listItem.classList.add("notRead"); + statusBadge.textContent = "○"; + } + + listItem.appendChild(bookImage); + listItem.appendChild(bookInfo); + listItem.appendChild(statusBadge); + + orderedList.appendChild(listItem); + } +} + +readingList(); diff --git a/Sprint-3/reading-list/script.test.js b/Sprint-3/reading-list/script.test.js index 39bdd921d..cb2bb3357 100644 --- a/Sprint-3/reading-list/script.test.js +++ b/Sprint-3/reading-list/script.test.js @@ -1,82 +1,59 @@ -const path = require("path"); -const { JSDOM } = require("jsdom"); - -let page = null; - -beforeEach(async () => { - page = await JSDOM.fromFile(path.join(__dirname, "index.html"), { - resources: "usable", - runScripts: "dangerously", - }); - - // do this so students can use element.innerText which jsdom does not implement - Object.defineProperty(page.window.HTMLElement.prototype, "innerText", { - get() { - return this.textContent; - }, - set(value) { - this.textContent = value; - }, - }); - - return new Promise((res) => { - page.window.document.addEventListener("load", res); - }); -}); - -afterEach(() => { - page = null; -}); - -describe("Reading list", () => { - test("renders a list of books with author and title", () => { - const readingList = page.window.document.querySelector("#reading-list"); - - expect(readingList).toHaveTextContent("The Design of Everyday Things"); - expect(readingList).toHaveTextContent("Don Norman"); - - expect(readingList).toHaveTextContent("The Most Human Human"); - expect(readingList).toHaveTextContent("Brian Christian"); - - expect(readingList).toHaveTextContent("The Pragmatic Programmer"); - expect(readingList).toHaveTextContent("Andrew Hunt"); - }); - test("each book in the list has an image", () => { - const firstLi = page.window.document.querySelector( - "#reading-list > :first-child" - ); - expect(firstLi).toContainHTML( - `` - ); - - const secondLi = page.window.document.querySelector( - "#reading-list > :nth-child(2)" - ); - expect(secondLi).toContainHTML( - `` - ); - - const thirdLi = page.window.document.querySelector( - "#reading-list > :nth-child(3)" - ); - expect(thirdLi).toContainHTML( - `` - ); - }); - test("background color changes depending on whether book has been read", () => { - const firstLi = page.window.document.querySelector( - "#reading-list > :first-child" - ); - expect(firstLi).toHaveStyle({ backgroundColor: "red" }); - - const secondLi = page.window.document.querySelector( - "#reading-list > :nth-child(2)" - ); - expect(secondLi).toHaveStyle({ backgroundColor: "green" }); - - const thirdLi = page.window.document.querySelector( - "#reading-list > :nth-child(3)" - ); - expect(thirdLi).toHaveStyle({ backgroundColor: "green" }); - }); -}); +// for the tests, do not modify this array of books +const books = [ + { + title: "The Design of Everyday Things", + author: "Don Norman", + alreadyRead: false, + bookCoverImage: "https://blackwells.co.uk/jacket/l/9780465050659.jpg", + }, + { + title: "The Most Human Human", + author: "Brian Christian", + alreadyRead: true, + bookCoverImage: + "https://images-na.ssl-images-amazon.com/images/I/41m1rQjm5tL._SX322_BO1,204,203,200_.jpg", + }, + { + title: "The Pragmatic Programmer", + author: "Andrew Hunt", + alreadyRead: true, + bookCoverImage: "https://blackwells.co.uk/jacket/l/9780135957059.jpg", + }, +]; + +function readingList() { + const orderedList = document.querySelector("#reading-list"); + + // Base condition: check if books array is empty + if (!books || books.length === 0) { + const emptyMessage = document.createElement("li"); + emptyMessage.className = "empty-message"; + emptyMessage.textContent = + "No books in your reading list yet. Add some books to get started!"; + orderedList.appendChild(emptyMessage); + return; + } + + for (const book of books) { + const listItem = document.createElement("li"); + + const bookImage = document.createElement("img"); + bookImage.src = book.bookCoverImage; + bookImage.alt = `Cover of ${book.title}`; + + const bookInfo = document.createElement("p"); + bookInfo.textContent = `${book.title} by ${book.author}`; + + listItem.append(bookImage, bookInfo); + + if (book.alreadyRead) { + listItem.classList.add("read"); + } else { + listItem.classList.add("notRead"); + } + + orderedList.appendChild(listItem); + } +} + +readingList(); diff --git a/Sprint-3/reading-list/style.css b/Sprint-3/reading-list/style.css index 74406e64f..ff0f344cc 100644 --- a/Sprint-3/reading-list/style.css +++ b/Sprint-3/reading-list/style.css @@ -1,159 +1,181 @@ /** - * Base styles to use at the start of the class - * - * Module: HTML/CSS - * Lesson: 1,2 - * Class: Scotland 2017 + * Reading List App Styles - Intermediate Level */ -html, -body { - font-family: "Source Sans Pro", -apple-system, system-ui, BlinkMacSystemFont, - "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; +/* Reset default browser styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; } -.site-footer { - margin-top: 4em; +body { + font-family: Arial, sans-serif; + background-color: #f0f0f0; + padding: 20px; } -.site-footer p { - border-top: 1px solid #dccdc6; - padding-top: 2em; - padding-bottom: 2em; +/* Main container */ +#content { + max-width: 800px; + margin: 0 auto; + background-color: white; + border-radius: 8px; + padding: 30px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } -.navbar-brand > img { - max-height: 40px; - width: auto; +/* Page heading */ +h1 { + text-align: center; + color: #333; + margin-bottom: 20px; + font-size: 28px; } -.navbar-light .navbar-nav .nav-link { - color: #292b2c; - font-weight: 600; - text-transform: uppercase; +/* Legend section */ +.legend { + text-align: center; + margin-bottom: 25px; + padding-bottom: 15px; + border-bottom: 2px solid #ddd; } -/* Buttons */ -.btn { - border-radius: 0.15em; +.legend-item { + display: inline-block; + margin: 0 15px; + font-size: 14px; + color: #666; } -/* Alert */ -.alert { - position: relative; - margin-top: 2em; - margin-bottom: 3em; - padding-top: 1.5em; - padding-bottom: 1.5em; - border: 1px solid #dccdc6; - border-radius: 0; - font-size: 0.85rem; - line-height: 1.3em; - background: transparent; - color: #292b2c; +.legend-badge { + display: inline-block; + width: 22px; + height: 22px; + border-radius: 50%; + text-align: center; + line-height: 20px; + font-weight: bold; + margin-right: 5px; } -.alert:before { - content: ""; - position: absolute; - left: -1px; - top: 0; - height: 100%; - width: 1px; - background: #ce5f31; +.read-badge { + background-color: #d4edda; + color: #155724; + border: 2px solid #28a745; } -/* Jumbotron */ -.jumbotron { - border-radius: 0; +.unread-badge { + background-color: #fff3cd; + color: #856404; + border: 2px solid #ffc107; } -/* Headings */ -.heading-underline { - position: relative; - margin-bottom: 2em; - padding-bottom: 0.5em; - border-bottom: 1px solid #dccdc6; - font-size: 1rem; - font-weight: 600; - text-transform: uppercase; +/* Ordered list */ +#reading-list { + list-style: decimal; + padding-left: 0; } -.heading-underline:before { - content: ""; - position: absolute; - bottom: -1px; - left: 0; - width: 25%; - height: 1px; - max-width: 100px; - background: #ce5f31; +#reading-list li { + display: flex; + align-items: center; + padding: 15px; + margin-bottom: 15px; + border-radius: 6px; + background-color: #f8f9fa; + border-left: 4px solid #ddd; } -/* Article */ -.article { - margin-bottom: 2em; +/* Book cover image */ +#reading-list li img { + width: 60px; + height: 90px; + border-radius: 4px; + margin-right: 15px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } -.article-title { - margin-bottom: 0.5em; - font-weight: 700; +/* Book information text */ +#reading-list li p { + flex: 1; + font-size: 16px; + color: #333; + margin: 0; } -.article-read-more a { - font-size: 0.85em; - font-weight: 700; - text-decoration: uppercase; +/* Status indicator */ +.status-badge { + width: 28px; + height: 28px; + border-radius: 50%; + text-align: center; + line-height: 24px; + font-weight: bold; + font-size: 16px; + margin-left: 15px; } -.article-read-more a:hover, -.article-read-more a:focus { - text-decoration: none; +/* Read books styling */ +#reading-list li.read { + background-color: #e8f5e9; + border-left-color: #28a745; } -.article-read-more .fa { - margin-right: 0.5em; - color: #ce5f31; +#reading-list li.read .status-badge { + background-color: #d4edda; + color: #155724; + border: 2px solid #28a745; } -.article-read-more:last-child { - margin-bottom: 0; +/* Unread books styling */ +#reading-list li.notRead { + background-color: #fff8e1; + border-left-color: #ffc107; } -.red { - background-color: red; +#reading-list li.notRead .status-badge { + background-color: #fff3cd; + color: #856404; + border: 2px solid #ffc107; } -.addArticle { - margin-bottom: 10px; +/* Empty state message */ +.empty-message { + text-align: center; + padding: 40px 20px; + background-color: #f8f9fa; + border: 2px dashed #ccc; + color: #666; + font-style: italic; + list-style: none; } -#addArticleBtn { - margin-left: 20px; - height: 37px; -} +/* Mobile responsive */ +@media screen and (max-width: 600px) { + body { + padding: 10px; + } -.colorButton { - margin-bottom: 20px; - margin-right: 20px; - width: 100px; - height: 50px; -} + #content { + padding: 20px; + } -#blueBtn { - background: #588fbd; -} + h1 { + font-size: 24px; + } -#orangeBtn { - background: #f0ad4e; -} + .legend-item { + display: block; + margin: 5px 0; + } -#greenBtn { - background: #87ca8a; -} + #reading-list li img { + width: 50px; + height: 75px; + } -@media screen and (min-width: 992px) { - .navbar-brand > img { - max-height: 80px; + #reading-list li p { + font-size: 14px; } }