diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 244efafc7f7..6d8f7bf9827 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -2087,9 +2087,9 @@ "title": "94", "intro": [] }, - "95": { - "title": "95", - "intro": [] + "lab-book-inventory-app": { + "title": "Build a Book Inventory App", + "intro": ["For this lab, you will create a book inventory app."] }, "96": { "title": "96", @@ -2634,8 +2634,8 @@ "title": "230", "intro": [] }, - "230": { - "title": "230", + "231": { + "title": "231", "intro": [] }, "232": { diff --git a/client/src/pages/learn/front-end-development/lab-book-inventory-app/index.md b/client/src/pages/learn/front-end-development/lab-book-inventory-app/index.md new file mode 100644 index 00000000000..c7690bd17c1 --- /dev/null +++ b/client/src/pages/learn/front-end-development/lab-book-inventory-app/index.md @@ -0,0 +1,9 @@ +--- +title: Introduction to the Build a Book Inventory App +block: lab-book-inventory-app +superBlock: front-end-development +--- + +## Introduction to the Build a Book Inventory App + +For this lab, you will create a book inventory app. diff --git a/curriculum/challenges/_meta/lab-book-inventory-app/meta.json b/curriculum/challenges/_meta/lab-book-inventory-app/meta.json new file mode 100644 index 00000000000..03f7bed9e65 --- /dev/null +++ b/curriculum/challenges/_meta/lab-book-inventory-app/meta.json @@ -0,0 +1,11 @@ +{ + "name": "Build a Book Inventory App", + "blockType": "lab", + "isUpcomingChange": true, + "usesMultifileEditor": true, + "dashedName": "lab-book-inventory-app", + "order": 95, + "superBlock": "front-end-development", + "challengeOrder": [{ "id": "66a207974c806a19d6607073", "title": "Build a Book Inventory App" }], + "helpCategory": "HTML-CSS" +} diff --git a/curriculum/challenges/english/25-front-end-development/lab-book-inventory-app/66a207974c806a19d6607073.md b/curriculum/challenges/english/25-front-end-development/lab-book-inventory-app/66a207974c806a19d6607073.md new file mode 100644 index 00000000000..7439d3fb6ad --- /dev/null +++ b/curriculum/challenges/english/25-front-end-development/lab-book-inventory-app/66a207974c806a19d6607073.md @@ -0,0 +1,635 @@ +--- +id: 66a207974c806a19d6607073 +title: Build a Book Inventory App +challengeType: 14 +dashedName: build-a-book-inventory-app +--- + +# --description-- + + +**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. + +**User Stories:** + +1. You should have an `h1` element with the text `Book Inventory`. +1. You should have a `table` element with columns named `Title`, `Author`, `Category`, `Status`, and `Rate`. +1. Your table should have at least four rows, the first for the column headings and the rest filled with book information. +1. Each table row inside the table body should have either the class `read`, `to-read`, or `in-progress`. +1. `td` elements of the `Status` column should contain a `span` element with the `class` of `status` surrounding the text `Read`, `To Read`, or `In Progress`, depending on the class of that row. +1. `td` elements of the `Rate` column should contain a `span` element with the `class` of `rate` wrapping three empty `span` elements. +1. `.rate` elements placed inside `.read` rows should have an additional class with the value of either `one`, `two`, or `three`, depending on the personal rate. This value should come after `rate`. +1. You should create three attribute selectors to target the elements with the class of `read`, `to-read`, and `in-progress`, and set their `background-image` property to use a `linear-gradient` of your choice. +1. You should set the `display` property of each `span` element to `inline-block`. +1. You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `to-read` and set their `border` and `background-image` properties. +1. You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `read` and set their `border` and `background-image` properties. +1. You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `in-progress` and set their `border` and `background-image` properties. +1. You should use an attribute selector to target the `span` elements with the class of `status` and the `span` elements with the class value starting with `rate` and set their `height`, `width`, and `padding` properties. +1. You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `border`, `border-radius`, `margin`, `height`, `width`, and `background-color` properties. +1. You should use an attribute selector to target the first descendant of `span` elements that have `one` as a part of their `class` value and set its `background-image` property to use a `linear-gradient`. +1. You should use an attribute selector to target the first two descendants of `span` elements that have `two` as a part of their `class` value and set their `background-image` property to use a `linear-gradient`. +1. You should use an attribute selector to target the three `span` elements that are descendants of `span` elements that have `three` as a part of their `class` value and set their `background-image` property to use a `linear-gradient`. + +# --hints-- + +You should have an `h1` element with the text `Book Inventory`. + +```js +assert.equal(document.querySelector('h1')?.innerText, 'Book Inventory'); +``` + +You should have only one `h1` element. + +```js +assert.equal(document.querySelectorAll('h1')?.length, 1); +``` + +You should have a `table` element after your `h1` element. + +```js +assert.equal(document.querySelector('table')?.previousElementSibling?.tagName, 'H1') +``` + +Your `table` element should have five columns. + +```js +assert.equal(document.querySelectorAll('th')?.length, 5); +``` + +Your first column should have the text `Title` as the heading. + +```js +assert.equal(document.querySelectorAll('th')[0]?.innerText, 'Title'); +``` + +Your second column should have the text `Author` as the heading. + +```js +assert.equal(document.querySelectorAll('th')[1]?.innerText, 'Author'); +``` + +Your third column should have the text `Category` as the heading. + +```js +assert.equal(document.querySelectorAll('th')[2]?.innerText, 'Category'); +``` + +Your fourth column should have the text `Status` as the heading. + +```js +assert.equal(document.querySelectorAll('th')[3]?.innerText, 'Status'); +``` + +Your fifth column should have the text `Rate` as the heading. + +```js +assert.equal(document.querySelectorAll('th')[4]?.innerText, 'Rate'); +``` + +Your table should have at least four rows. + +```js +const rows = document.querySelectorAll('tr'); +assert.isAtLeast(rows.length, 4); +``` + +Each table row except the heading row should have either the class `read`, `to-read`, or `in-progress`. + +```js +const rows = document.querySelectorAll('tr'); +assert.isAtLeast(rows.length, 4); +for (let i = 1; i < rows.length; i++) { + assert( + ( + rows[i].classList).contains('read') + || (rows[i].classList).contains('to-read') + || (rows[i].classList).contains('in-progress') + ); +} +``` + +`td` elements of the `Status` column should contain a `span` element. + +```js +const statusColumnData = document.querySelectorAll('td:nth-child(4)'); +assert.isAbove(statusColumnData.length, 0); +for (let e of statusColumnData) { + assert.equal(e?.children[0]?.tagName, 'SPAN'); +} +``` + +Each `span` element of the `Status` column should have the class of `status`. + +```js +const statusSpans = document.querySelectorAll('tr td:nth-child(4) :first-child'); +assert.isAbove(statusSpans.length, 0); +for (let e of statusSpans) { + assert(e.classList.contains('status')); +} +``` + +Each `.status` element should have the text `Read`, `To Read`, or `In Progress`, depending on the class of its row. + +```js +const statusSpans = document.querySelectorAll('tr td:nth-child(4) :first-child'); +const rows = Array.from(document.querySelectorAll('tr')).slice(1); +assert.isAbove(statusSpans.length, 0); +for (let i = 0; i < rows.length; i++) { + switch (statusSpans[i]?.innerText) { + case 'Read': + assert(rows[i].classList.contains('read')); + break; + case 'To Read': + assert(rows[i].classList.contains('to-read')); + break; + case 'In Progress': + assert(rows[i].classList.contains('in-progress')); + break; + default: + assert(false); + } +} +``` + +`td` elements of the `Rate` column should contain a `span` element. + +```js +const rateColumnData = document.querySelectorAll('tr td:last-child'); +assert.isAbove(rateColumnData.length, 0); +for (let e of rateColumnData) { + assert.equal(e.children[0]?.tagName, 'SPAN') +} +``` + +Each `span` element which is a direct child of a `td` element of the `Rate` column should have the class of `rate` as the first class. + +```js +const rateSpans = document.querySelectorAll('tr td:last-child > span:first-child'); +assert.isAbove(rateSpans.length, 0); +for (let e of rateSpans) { + assert.equal(e.classList[0], 'rate'); +} +``` + +Each `.rate` element should contain three empty `span` elements. + +```js +const rateSpans = document.getElementsByClassName('rate'); +assert.isAbove(rateSpans.length, 0); +for (let e of rateSpans) { + assert.equal(e.children.length, 3); + for (let child of e.children) { + assert.equal(child.tagName, 'SPAN'); + assert.equal(child.innerText.length, 0); + } +} +``` + +`.rate` elements placed inside `.read` rows should have an additional class after the `rate` class with the value of either `one`, `two`, or `three`, depending on the personal rate. + +```js +const readBooksRates = document.querySelectorAll('.read .rate'); +assert.isAbove(readBooksRates.length, 0); +for (let e of readBooksRates) { + assert(['one', 'two', 'three'].includes(e.classList[1])); +} +``` + +You should have an attribute selector to target rows that have the class of `read`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('tr[class="read"]')); +``` + +You should use an attribute selector to target rows that have the class of `read` and set their `background-image` property to a linear gradient of your choice. + +```js +assert.include(new __helpers.CSSHelp(document).getStyle('tr[class="read"]')?.backgroundImage, 'linear-gradient('); +``` + +You should have an attribute selector to target rows that have the class of `to-read`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('tr[class="to-read"]')); +``` + +You should use an attribute selector to target rows that have the class of `to-read` and set their `background-image` property to a linear gradient of your choice. + +```js +assert.include(new __helpers.CSSHelp(document).getStyle('tr[class="to-read"]')?.backgroundImage, 'linear-gradient('); +``` + +You should have an attribute selector to target rows that have the class of `in-progress`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('tr[class="in-progress"]')); +``` + +You should use an attribute selector to target rows that have the class of `in-progress` and set their `background-image` property to a linear gradient of your choice. + +```js +assert.include(new __helpers.CSSHelp(document).getStyle('tr[class="in-progress"]')?.backgroundImage, 'linear-gradient('); +``` + +You should set the `display` property of each `span` element to `inline-block`. + +```js +assert.equal(new __helpers.CSSHelp(document).getStyle('span')?.getPropVal('display'), 'inline-block'); +``` + +You should have an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `to-read`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('tr[class="to-read"] span[class="status"]')); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `to-read` and set their `border` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('tr[class="to-read"] span[class="status"]')?.border); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `to-read` and set their `background-image` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('tr[class="to-read"] span[class="status"]')?.backgroundImage); +``` + +You should have an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `read`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('tr[class="read"] span[class="status"]')); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `read` and set their `border` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('tr[class="read"] span[class="status"]')?.border); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `read` and set their `background-image` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('tr[class="read"] span[class="status"]')?.backgroundImage); +``` + +You should have an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `in-progress`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('tr[class="in-progress"] span[class="status"]')); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `in-progress` and set their `border` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('tr[class="in-progress"] span[class="status"]')?.border); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` that are descendants of `tr` elements with the class of `in-progress` and set their `background-image` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('tr[class="in-progress"] span[class="status"]')?.backgroundImage); +``` + +You should have an attribute selector to target the `span` elements with the class of `status` and the `span` elements with the class value starting with `rate`. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class="status"], span[class^="rate"]'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class^="rate"], span[class="status"]'); +assert.exists(selector1 || selector2); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` and the `span` elements with the class value starting with `rate` and set their `height` property. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class="status"], span[class^="rate"]'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class^="rate"], span[class="status"]'); +assert(selector1?.height || selector2?.height); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` and the `span` elements with the class value starting with `rate` and set their `width` property. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class="status"], span[class^="rate"]'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class^="rate"], span[class="status"]'); +assert(selector1?.width || selector2?.width); +``` + +You should use an attribute selector to target the `span` elements with the class of `status` and the `span` elements with the class value starting with `rate` and set their `padding` property. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class="status"], span[class^="rate"]'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class^="rate"], span[class="status"]'); +assert(selector1?.padding || selector2?.padding); +``` + +You should have an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate`. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')); +``` + +You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `border` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')?.getPropVal('border')); +``` + +You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `border-radius` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')?.getPropVal('border-radius')); +``` + +You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `margin` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')?.getPropVal('margin')); +``` + +You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `height` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')?.getPropVal('height')); +``` + +You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `width` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')?.getPropVal('width')); +``` + +You should use an attribute selector to target the `span` elements which are direct children of `span` elements with the `class` value starting with `rate` and set their `background-color` property. + +```js +assert(new __helpers.CSSHelp(document).getStyle('span[class^="rate"] > span')?.getPropVal('background-color')); +``` + +You should have an attribute selector to target the first descendant of `span` elements that have `one` as a part of their `class` value. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class~="one"] :first-child'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class~="one"] :nth-child(1)'); +assert.exists(selector1 || selector2); +``` + +You should use an attribute selector to target the first descendant of `span` elements that have `one` as a part of their `class` value and set its `background-image` property to use a `linear-gradient`. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class~="one"] :first-child'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class~="one"] :nth-child(1)'); +assert(selector1?.getPropVal('background-image').includes('linear-gradient(') || selector2?.getPropVal('background-image').includes('linear-gradient(')); +``` + +You should have an attribute selector to target the first two descendants of `span` elements that have `two` as a part of their `class` value. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :nth-child(1), span[class~="two"] :nth-child(2)'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :nth-child(2), span[class~="two"] :nth-child(1)'); +const selector3 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :first-child, span[class~="two"] :nth-child(2)'); +const selector4 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :nth-child(2), span[class~="two"] :first-child'); +assert.exists(selector1 || selector2 || selector3 || selector4); +``` + +You should use an attribute selector to target the first two descendants of `span` elements that have `two` as a part of their `class` value and set their `background-image` property to use a `linear-gradient`. + +```js +const selector1 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :nth-child(1), span[class~="two"] :nth-child(2)'); +const selector2 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :nth-child(2), span[class~="two"] :nth-child(1)'); +const selector3 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :first-child, span[class~="two"] :nth-child(2)'); +const selector4 = new __helpers.CSSHelp(document).getStyle('span[class~="two"] :nth-child(2), span[class~="two"] :first-child'); +const selectors = [selector1, selector2, selector3, selector4]; +let isTrue = false; +for (let selector of selectors) { + if (selector?.backgroundImage.includes('linear-gradient(')) { + isTrue = true; + } +} +assert(isTrue); +``` + +You should have an attribute selector to target the `span` elements that are descendants of `span` elements that have `three` as a part of their `class` value. + +```js +assert.exists(new __helpers.CSSHelp(document).getStyle('span[class~="three"] span')); +``` + +You should use an attribute selector to target the `span` elements that are descendants of `span` elements that have `three` as a part of their `class` value and set their `background-image` property to use a `linear-gradient`. + +```js +assert.include(new __helpers.CSSHelp(document).getStyle('span[class~="three"] span')?.getPropVal('background-image'), 'linear-gradient('); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + Book Inventory + + + + + + + + +``` + +```css + +``` + +# --solutions-- + +```html + + + + + + Book Inventory + + + + +
+

Book Inventory

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TitleAuthorCategoryStatusRate
The Three MusketeersAlexandre DumasHistorical NovelRead + + + +
The PlagueAlbert CamusPhilosofical NovelTo Read + + + +
The MetamorphosisFranz KafkaNovellaRead + + + +
Dead SoulsNikolai GogolPicaresqueRead + + + +
Lord of the FliesWilliam GoldingAllegorical NovelIn Progress + + + +
Do Androids Dream of Electric Sheep?Philip K. DickScience FictionRead + + + +
+
+ + + +``` + +```css +* { + box-sizing: border-box; + font-family: Arial, sans-serif; +} + +table { + border-collapse: collapse; + width: 100vw; +} + +thead { + background-image: linear-gradient(hsl(197, 92%, 77%, 0.60), hsl(197, 92%, 50%, 0.60)); +} + +th { + padding: 0.4em; +} + +td { + text-align: center; + padding: 0.3em; +} + +tr[class="read"] { + background-image: linear-gradient(hsl(120, 100%, 85%, 0.6), hsl(120, 100%, 65%, 0.6)); +} + +tr[class="to-read"] { + background-image: linear-gradient(hsl(120, 20%, 95%, 0.6), hsl(120, 20%, 75%, 0.6)); +} + +tr[class="in-progress"] { + background-image: linear-gradient(hsl(40, 100%, 85%, 0.60), hsl(40, 100%, 65%, 0.6)); +} + +span { + display: inline-block; +} + +span[class="status"] { + border-radius: 20%/60%; +} + +span[class="status"], +span[class^="rate"] { + height: 1.85em; + width: 7em; + padding: 0.3em; +} + +tr[class="to-read"] span[class="status"] { + border: 0.1em solid hsl(5, 100%, 45%); + background-image: linear-gradient(hsl(5, 100%, 75%), hsl(5, 100%, 50%)); +} + +tr[class="read"] span[class="status"] { + border: 0.1em solid hsl(120, 75%, 45%); + background-image: linear-gradient(hsl(120, 75%, 75%), hsl(120, 75%, 50%)); +} + +tr[class="in-progress"] span[class="status"] { + border: 0.1em solid hsl(40, 90%, 40%); + background-image: linear-gradient(hsl(40, 90%, 75%), hsl(40, 90%, 50%)); +} + +span[class^="rate"] > span { + border: 0.1em solid hsl(0, 0%, 50%); + border-radius: 50%; + background-color: hsl(0, 15%, 95%); + height: 100%; + width: 20%; + margin: 0.1em; +} + +span[class~="three"] span { + background-image: linear-gradient(hsl(50, 100%, 70%), hsl(50, 100%, 50%)); +} + +span[class~="two"] :nth-child(1), +span[class~="two"] :nth-child(2) { + background-image: linear-gradient(hsl(50, 100%, 70%), hsl(50, 100%, 50%)); +} + +span[class~="one"] :first-child { + background-image: linear-gradient(hsl(50, 100%, 70%), hsl(50, 100%, 50%)); +} +```