feat(curriculum): add book inventory lab (#55678)

pull/55847/head
Dario-DC 2024-08-13 16:17:26 +02:00 committed by GitHub
parent b9893bb4d6
commit 90b2cfbfeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 660 additions and 5 deletions

View File

@ -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": {

View File

@ -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.

View File

@ -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"
}

View File

@ -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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Book Inventory</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
</body>
</html>
```
```css
```
# --solutions--
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Book Inventory</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<h1>Book Inventory</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Category</th>
<th>Status</th>
<th>Rate</th>
</tr>
</thead>
<tbody>
<tr class="read">
<td>The Three Musketeers</td>
<td>Alexandre Dumas</td>
<td>Historical Novel</td>
<td><span class="status">Read</span></td>
<td>
<span class="rate three">
<span></span><span></span><span></span>
</span>
</td>
</tr>
<tr class="to-read">
<td>The Plague</td>
<td>Albert Camus</td>
<td>Philosofical Novel</td>
<td><span class="status">To Read</span></td>
<td>
<span class="rate">
<span></span><span></span><span></span>
</span>
</td>
</tr>
<tr class="read">
<td>The Metamorphosis</td>
<td>Franz Kafka</td>
<td>Novella</td>
<td><span class="status">Read</span></td>
<td>
<span class="rate one">
<span></span><span></span><span></span>
</span>
</td>
</tr>
<tr class="read">
<td>Dead Souls</td>
<td>Nikolai Gogol</td>
<td>Picaresque</td>
<td><span class="status">Read</span></td>
<td>
<span class="rate two">
<span></span><span></span><span></span>
</span>
</td>
</tr>
<tr class="in-progress">
<td>Lord of the Flies</td>
<td>William Golding</td>
<td>Allegorical Novel</td>
<td><span class="status">In Progress</span></td>
<td>
<span class="rate">
<span></span><span></span><span></span>
</span>
</td>
</tr>
<tr class="read">
<td>Do Androids Dream of Electric Sheep?</td>
<td>Philip K. Dick</td>
<td>Science Fiction</td>
<td><span class="status">Read</span></td>
<td>
<span class="rate two">
<span></span><span></span><span></span>
</span>
</td>
</tr>
</tbody>
</table>
</main>
</body>
</html>
```
```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%));
}
```