diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 53e0a70028e..bb3d129e1a8 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -4829,6 +4829,12 @@ "In these lectures, you will learn the fundamentals of JavaScript objects, including how to create them, access their properties, and understand the difference between primitive and non-primitive data types." ] }, + "lab-cargo-manifest-validator": { + "title": "Build a Cargo Manifest Validator", + "intro": [ + "In this lab, you will use JavaScript to normalize and validate cargo manifests." + ] + }, "lecture-working-with-json": { "title": "Working with JSON", "intro": [ diff --git a/curriculum/challenges/english/blocks/lab-cargo-manifest-validator/69a56b5069ca99f7317e6e19.md b/curriculum/challenges/english/blocks/lab-cargo-manifest-validator/69a56b5069ca99f7317e6e19.md new file mode 100644 index 00000000000..7ed0a6f8a7e --- /dev/null +++ b/curriculum/challenges/english/blocks/lab-cargo-manifest-validator/69a56b5069ca99f7317e6e19.md @@ -0,0 +1,861 @@ +--- +id: 69a56b5069ca99f7317e6e19 +title: Build a Cargo Manifest Validator +challengeType: 26 +dashedName: lab-cargo-manifest-validator +--- + +# --description-- + +In this lab, you will use JavaScript to normalize and validate cargo manifests. A cargo manifest is a document that typically lists goods being transported (for example, by ship or train) and includes details about those goods. + +Each cargo manifest will be represented as an object with the following properties: + +- `containerId`: a positive integer identifying the associated cargo container. +- `destination`: a non-empty string (after trimming whitespace) denoting the cargo's target destination. +- `weight`: a positive number representing the cargo's weight. +- `unit`: a string describing the units for the cargo's weight property (either `"kg"` for kilograms or `"lb"` for pounds). +- `hazmat`: a boolean value indicating whether hazardous material handling is needed. + +Example cargo manifest object: + +```js +{ + containerId: 1, + destination: "Monterey, California, USA", + weight: 831, + unit: "lb", + hazmat: false +} +``` + +**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. + +**User Stories:** + +1. You should implement a function named `normalizeUnits` with a `manifest` parameter. + - The function must not mutate the original manifest object and must always return a new object where `weight` is normalized to kilograms and `unit` is set to `"kg"`. + - If the weight of the manifest object is expressed in pounds (`unit: "lb"`), the function should convert the `weight` to kilograms using the approximate conversion, 1 lb = 0.45 kg, and update the `unit` accordingly. + - If the weight is already expressed in kilograms (`unit: "kg"`), the `weight` and `unit` should remain unchanged. +2. You should implement a function named `validateManifest` with a `manifest` parameter. + - The function must not mutate the original manifest object and must always return a new object. + - If the input manifest is valid (no missing or invalid properties), the function should return an empty object. + - If the input manifest is not valid, the function should return an object containing entries for each missing or invalid property. Missing properties should have the value `"Missing"` and invalid properties should have the value `"Invalid"`. + + Example return value where the input object is missing the `destination` property and has an invalid `weight` property: + + ```js + { + destination: "Missing", + weight: "Invalid" + } + ``` + +3. You should implement a function named `processManifest` with a `manifest` parameter. The function should log: + - If the manifest object is valid, `Validation success: ${containerId}` and then the manifest's weight in kilograms as such, `Total weight: ${weight} kg`. Use `normalizeUnits()` for this conversion. + - If the manifest object is not valid, `Validation error: ${containerId}` and then the object returned by calling `validateManifest()` with the manifest object. + **Note:** each of these two cases should have two `console.log()` calls. + +# --before-each-- + +```js +const _validManifests = [ + { + containerId: 4, + destination: "Monterey, California, USA", + weight: 831, + unit: "lb", + hazmat: false + }, + { + containerId: 5, + destination: "Montreal, Quebec, Canada", + weight: 151, + unit: "kg", + hazmat: false + } +]; + +const _invalidManifests = [ + { + containerId: -6, + destination: 123, + weight: 0, + unit: "pounds", + hazmat: true + }, + { + containerId: 0, + weight: -21, + unit: "KG" + }, + { + containerId: "eight", + destination: "Guadalajara, Jalisco, Mexico", + weight: 9001, + hazmat: "no" + }, + { + destination: " ", + weight: NaN, + unit: "kg", + hazmat: 1 + }, + { + containerId: 10, + destination: " ", + }, +]; + +const _testObj = { + containerId: 1.51, + destination: "Lavender Town", + weight: NaN, +}; + +console.log = () => {}; +``` + +# --hints-- + +You should have a function named `normalizeUnits` with a `manifest` parameter. + +```js +assert.isFunction(normalizeUnits); +const funcString = normalizeUnits.toString(); +const params = __helpers.getFunctionParams(funcString); +assert.equal(params.length, 1); +assert.equal(params[0].name, 'manifest'); +``` + +Calling `normalizeUnits()` with `{ containerId: 68, destination: "Salinas", weight: 101, unit: "lb", hazmat: true }` should return the new object `{ containerId: 68, destination: "Salinas", weight: 45.45, unit: "kg", hazmat: true }` without mutating the source input. + +```js +const testObj = { + containerId: 68, + destination: "Salinas", + weight: 101, + unit: "lb", + hazmat: true +}; + +const expected = { + containerId: 68, + destination: "Salinas", + weight: 45.45, + unit: "kg", + hazmat: true +}; + +const copy = normalizeUnits(testObj); + +assert.isObject(copy); +assert.deepEqual(copy, expected, "normalizeUnits() did not return the expected normalized object"); +assert.notStrictEqual(copy, testObj, "normalizeUnits() should return a new object."); +assert.strictEqual(testObj.containerId, 68, "Original object should not be mutated."); +assert.strictEqual(testObj.destination, "Salinas", "Original object should not be mutated."); +assert.strictEqual(testObj.weight, 101, "Original object should not be mutated."); +assert.strictEqual(testObj.unit, "lb", "Original object should not be mutated."); +assert.strictEqual(testObj.hazmat, true, "Original object should not be mutated."); +assert.equal(Object.keys(testObj).length, 5, "Original object should not be mutated."); +``` + +Your `normalizeUnits` function should return a copy of the input manifest object with its `weight` normalized to kilograms and its `unit` set to `"kg"`. Use the approximate conversion `1 lb = 0.45 kg` for the weight conversion. + +```js +_validManifests.forEach((obj, i) => { + const result = normalizeUnits(obj); + assert.isObject(result, "normalizeUnits should return an object"); + assert.notStrictEqual(result, obj, "normalizeUnits must return a new object, not modify the original"); + const expectedWeight = obj.unit === "lb" ? obj.weight * 0.45 : obj.weight; + const expected = { + ...obj, + weight: expectedWeight, + unit: "kg" + }; + + assert.deepEqual( + result, + expected, + `normalizeUnits did not return the expected normalized object for manifest at index ${i}` + ); +}); +``` + +Your `normalizeUnits` function should return a new copy of the input `manifest` object without mutating the original. + +```js +const original = { + containerId: 151, + destination: "Saffron City", + weight: 151, + unit: "lb", + hazmat: false +}; + +const expected = { + containerId: 151, + destination: "Saffron City", + weight: 67.95, + unit: "kg", + hazmat: false +}; + +const copy = normalizeUnits(original); +assert.isObject(copy); +assert.deepEqual(copy, expected); +assert.notStrictEqual(copy, original, "normalizeUnits() should return a new object."); +assert.strictEqual(original.weight, 151, "Original object should not be mutated."); +assert.strictEqual(original.unit, "lb", "Original object should not be mutated."); +assert.equal(Object.keys(original).length, 5, "Original object should not be mutated."); +``` + +You should have a function named `validateManifest` with a `manifest` parameter. + +```js +assert.isFunction(validateManifest); +const funcString = validateManifest.toString(); +const params = __helpers.getFunctionParams(funcString); +assert.equal(params.length, 1); +assert.equal(params[0].name, 'manifest'); +``` + +Calling `validateManifest()` with `{ containerId: 1, destination: "Santa Cruz", weight: 304, unit: "kg", hazmat: false }` should return the new object `{}`. + +```js +const testObj = { + containerId: 1, + destination: "Santa Cruz", + weight: 304, + unit: "kg", + hazmat: false +}; + +const expected = {}; + +const copy = validateManifest(testObj); + +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for a valid manifest"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.strictEqual(testObj.containerId, 1, "Original object should not be mutated"); +assert.strictEqual(testObj.destination, "Santa Cruz", "Original object should not be mutated"); +assert.strictEqual(testObj.weight, 304, "Original object should not be mutated"); +assert.strictEqual(testObj.unit, "kg", "Original object should not be mutated"); +assert.strictEqual(testObj.hazmat, false, "Original object should not be mutated"); +``` + +If the input manifest object is valid, your `validateManifest` function should return an empty object `{}`. + +```js +const solution = {} +for (const obj of _validManifests) { + assert.deepEqual( + validateManifest(obj), + solution, + "validateManifest() should return an empty object for valid manifests." + ); +} +``` + +Calling `validateManifest()` with `{}` should return the new object `{ containerId: "Missing", destination: "Missing", weight: "Missing", unit: "Missing", hazmat: "Missing" }` without mutating the source input. + +```js +const testObj = { +}; + +const expected = { + containerId: "Missing", + destination: "Missing", + weight: "Missing", + unit: "Missing", + hazmat: "Missing" +}; + +const copy = validateManifest(testObj); +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for missing properties"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.equal(Object.keys(testObj).length, 0, "validateManifest() should return a new object, not mutate the original") +``` + +Calling `validateManifest()` with `{ containerId: 0, destination: 405, weight: -84, unit: "pounds", hazmat: "no" }` should return the new object `{ containerId: "Invalid", destination: "Invalid", weight: "Invalid", unit: "Invalid", hazmat: "Invalid" }` without mutating the source input. + +```js +const testObj = { + containerId: 0, + destination: 405, + weight: -84, + unit: "pounds", + hazmat: "no" +}; + +const expected = { + containerId: "Invalid", + destination: "Invalid", + weight: "Invalid", + unit: "Invalid", + hazmat: "Invalid" +}; + +const copy = validateManifest(testObj); +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for invalid properties"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.strictEqual(testObj.containerId, 0, "Original object should not be mutated"); +assert.strictEqual(testObj.destination, 405, "Original object should not be mutated"); +assert.strictEqual(testObj.weight, -84, "Original object should not be mutated"); +assert.strictEqual(testObj.unit, "pounds", "Original object should not be mutated"); +assert.strictEqual(testObj.hazmat, "no", "Original object should not be mutated"); +``` + +Calling `validateManifest()` with `{ containerId: -2 }` should return the new object `{ containerId: "Invalid", destination: "Missing", weight: "Missing", unit: "Missing", hazmat: "Missing" }` without mutating the source input. + +```js +const testObj = { + containerId: -2, +}; + +const expected = { + containerId: "Invalid", + destination: "Missing", + weight: "Missing", + unit: "Missing", + hazmat: "Missing" +}; + +const copy = validateManifest(testObj); +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for invalid and missing properties"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.strictEqual(testObj.containerId, -2, "Original object should not be mutated"); +assert.equal(Object.keys(testObj).length, 1, "Original object should not be mutated"); +``` + +Calling `validateManifest()` with `{ containerId: 3.50 }` should return the new object `{ containerId: "Invalid", destination: "Missing", weight: "Missing", unit: "Missing", hazmat: "Missing" }` without mutating the source input. You can use `Number.isInteger()` to validate integer values. + +```js +const testObj = { + containerId: 3.50, +}; + +const expected = { + containerId: "Invalid", + destination: "Missing", + weight: "Missing", + unit: "Missing", + hazmat: "Missing" +}; + +const copy = validateManifest(testObj); +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for invalid and missing properties"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.strictEqual(testObj.containerId, 3.50, "Original object should not be mutated"); +assert.equal(Object.keys(testObj).length, 1, "Original object should not be mutated"); +``` + +Calling `validateManifest()` with `{ destination: " " }` should return the new object `{ containerId: "Missing", destination: "Invalid", weight: "Missing", unit: "Missing", hazmat: "Missing" }` without mutating the source input. You can use `String.trim()` to remove whitespace from a string. + +```js +const testObj = { + destination: " ", +}; + +const expected = { + containerId: "Missing", + destination: "Invalid", + weight: "Missing", + unit: "Missing", + hazmat: "Missing" +}; + +const copy = validateManifest(testObj); +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for invalid and missing properties"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.strictEqual(testObj.destination, " ", "Original object should not be mutated"); +assert.equal(Object.keys(testObj).length, 1, "Original object should not be mutated"); +``` + +Calling `validateManifest()` with `{ weight: NaN }` should return the new object `{ containerId: "Missing", destination: "Missing", weight: "Invalid", unit: "Missing", hazmat: "Missing" }` without mutating the source input. You can use `Number.isNaN()` to validate NaN values. + +```js +const testObj = { + weight: NaN, +}; + +const expected = { + containerId: "Missing", + destination: "Missing", + weight: "Invalid", + unit: "Missing", + hazmat: "Missing" +}; + +const copy = validateManifest(testObj); +assert.isObject(copy); +assert.deepEqual(copy, expected, "validateManifest() did not return the expected object for invalid and missing properties"); +assert.notStrictEqual(copy, testObj, "validateManifest() should return a new object, not mutate the original"); +assert.ok(Number.isNaN(testObj.weight), "Original object should not be mutated"); +assert.equal(Object.keys(testObj).length, 1, "Original object should not be mutated"); +``` + +If the input manifest object is not valid, your `validateManifest` function should return an object describing missing and/or invalid properties. + +```js +const solutions = [ + { containerId: 'Invalid', destination: 'Invalid', weight: 'Invalid', unit: 'Invalid' }, + { containerId: 'Invalid', destination: 'Missing', weight: 'Invalid', unit: 'Invalid', hazmat: 'Missing' }, + { containerId: 'Invalid', unit: 'Missing', hazmat: 'Invalid' }, + { containerId: 'Missing', destination: 'Invalid', weight: 'Invalid', hazmat: 'Invalid' }, + { destination: 'Invalid', weight: 'Missing', unit: 'Missing', hazmat: 'Missing' } +]; + +let i = 0; +for (const obj of _invalidManifests) { + const result = validateManifest(obj); + assert.deepEqual( + result, + solutions[i], + "validateManifest() should return an object with errors for invalid manifests." + ); + i += 1; +} +``` + +Your `validateManifest` function should return a new object without mutating the original. + +```js +const invalidOriginal = { + containerId: -151, + destination: "", + weight: -151, + unit: "kilograms", + hazmat: "idk" +}; + +const validOriginal = { + containerId: 151, + destination: "Pallet Town", + weight: 151, + unit: "kg", + hazmat: false +}; + +const res0 = validateManifest(invalidOriginal); +assert.isObject(res0); +assert.notStrictEqual(res0, invalidOriginal, "validateManifest() should return a new object."); +assert.strictEqual(invalidOriginal.containerId, -151, "Original object should not be mutated."); +assert.strictEqual(invalidOriginal.destination, "", "Original object should not be mutated."); +assert.strictEqual(invalidOriginal.weight, -151, "Original object should not be mutated."); +assert.strictEqual(invalidOriginal.unit, "kilograms", "Original object should not be mutated."); +assert.strictEqual(invalidOriginal.hazmat, "idk", "Original object should not be mutated."); +assert.equal(Object.keys(invalidOriginal).length, 5, "Original object should not be mutated."); + +const res1 = validateManifest(validOriginal); +assert.isObject(res1); +assert.notStrictEqual(res1, validOriginal, "validateManifest() should return a new object."); +assert.strictEqual(validOriginal.containerId, 151, "Original object should not be mutated."); +assert.strictEqual(validOriginal.destination, "Pallet Town", "Original object should not be mutated."); +assert.strictEqual(validOriginal.weight, 151, "Original object should not be mutated."); +assert.strictEqual(validOriginal.unit, "kg", "Original object should not be mutated."); +assert.strictEqual(validOriginal.hazmat, false, "Original object should not be mutated."); +assert.equal(Object.keys(validOriginal).length, 5, "Original object should not be mutated."); +``` + +You should have a function named `processManifest` with a `manifest` parameter. + +```js +assert.isFunction(processManifest); +const funcString = processManifest.toString(); +const params = __helpers.getFunctionParams(funcString); +assert.equal(params.length, 1); +assert.equal(params[0].name, 'manifest'); +``` + +Calling `processManifest()` with `{ containerId: 55, destination: "Carmel", weight: 400, unit: "lb", hazmat: false }` should first log `"Validation success: 55"` and then log `"Total weight: 180 kg"`. + +```js +const testObj = { + containerId: 55, + destination: "Carmel", + weight: 400, + unit: "lb", + hazmat: false +}; + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(testObj); + + const expectedCalls = [ + ["Validation success: 55"], + ["Total weight: 180 kg"] + ]; + assert.deepEqual(spy.calls[0], expectedCalls[0], "First log should be the validation success message"); + assert.deepEqual(spy.calls[1], expectedCalls[1], "Second log should be the normalized weight in kg"); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +If the input manifest object is valid, your `processManifest` function should first log the success message, `Validation success: ${containerId}`. + +```js +const testObj = { + containerId: 97, + destination: "Cerulean City", + weight: 200, + unit: "lb", + hazmat: false +} + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(testObj); + const expectedCalls = [ + ["Validation success: 97"], + ["Total weight: 90 kg"], + ] + assert.deepEqual(spy.calls[0], expectedCalls[0]); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +If the input manifest object is valid, your `processManifest` function should normalize it to kilograms using `normalizeUnits()` and then log: `Total weight: ${weight} kg`. + +```js +const testObj = { + containerId: 97, + destination: "Cerulean City", + weight: 200, + unit: "lb", + hazmat: false +} + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(testObj); + const expectedCalls = [ + ["Validation success: 97"], + ["Total weight: 90 kg"], + ] + assert.deepEqual(spy.calls[1], expectedCalls[1]); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +If the input manifest object is valid, your `processManifest` function should log a success message with the object's `containerId`, and then log the object's `weight` in kilograms. You should use `normalizeUnits()` for the conversion and have two `console.log()` calls. + +```js +const originalNormalize = normalizeUnits; +let called = false; +try { + normalizeUnits = function (...args) { + called = true; + return originalNormalize(...args); + }; + + processManifest(_validManifests[0]); + + assert.isTrue( + called, + "processManifest should call normalizeUnits() when the manifest is valid" + ); + +} catch (err) { + assert.fail(err); +} finally { + normalizeUnits = originalNormalize; +} + +const spy = __helpers.spyOn(console, "log"); + +try { + for (const obj of _validManifests) { + processManifest(obj); + } + const expectedCalls = [ + ["Validation success: 4"], + ["Total weight: 373.95 kg"], + ["Validation success: 5"], + ["Total weight: 151 kg"], + ] + assert.deepEqual(spy.calls, expectedCalls); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +Calling `processManifest()` with `{ containerId: -88, destination: "Soledad", weight: NaN }` should first log `Validation error: -88` and then log the object `{ containerId: "Invalid", weight: "Invalid", unit: "Missing", hazmat: "Missing" }`. + +```js +const testObj = { + containerId: -88, + destination: "Soledad", + weight: NaN +}; + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(testObj); + const expectedCalls = [ + ["Validation error: -88"], + [{ + containerId: "Invalid", + weight: "Invalid", + unit: "Missing", + hazmat: "Missing" + }] + ]; + + assert.deepEqual(spy.calls[0], expectedCalls[0], "First log should be the validation error message"); + assert.deepEqual(spy.calls[1], expectedCalls[1], "Second log should be the validation result object"); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +Calling `processManifest()` with `{ destination: "Watsonville", hazmat: true }` should first log `Validation error: undefined` and then log the object `{ containerId: "Missing", weight: "Missing", unit: "Missing" }`. + +```js +const testObj = { + destination: "Watsonville", + hazmat: true +}; + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(testObj); + const expectedCalls = [ + ["Validation error: undefined"], + [{ + containerId: "Missing", + weight: "Missing", + unit: "Missing" + }] + ]; + + assert.deepEqual(spy.calls[0], expectedCalls[0], "First log should be the validation error message"); + assert.deepEqual(spy.calls[1], expectedCalls[1], "Second log should be the validation result object"); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +If the input manifest object is not valid, your `processManifest` function should first log the error message, `Validation error: ${containerId}`. + +```js +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(_testObj); + const expectedCalls = [ + ["Validation error: 1.51"], + [{ + containerId: "Invalid", + weight: "Invalid", + unit: "Missing", + hazmat: "Missing" + }] + ]; + assert.deepEqual(spy.calls[0], expectedCalls[0]); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +If the input manifest object is not valid, your `processManifest` function should also log the object returned by calling `validateManifest()` with the original manifest object. Call `console.log()` directly with the returned object. + +```js +const originalValidate = validateManifest; +let called = false; + +try { + validateManifest = function (...args) { + called = true; + return originalValidate(...args); + }; + + processManifest(_invalidManifests[0]); + + assert.isTrue( + called, + "processManifest should call validateManifest() when the manifest is invalid" + ); + +} catch (err) { + assert.fail(err); +} finally { + validateManifest = originalValidate; +} + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(_testObj); + const expectedCalls = [ + ["Validation error: 1.51"], + [{ + containerId: "Invalid", + weight: "Invalid", + unit: "Missing", + hazmat: "Missing" + }] + ]; + assert.deepEqual(spy.calls[1], expectedCalls[1]); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +If the input manifest object is not valid, your `processManifest` function should log an error message with the object's `containerId`, and then log the object returned by calling `validateManifest()` with the input object. This should involve two `console.log()` calls. + +```js +const originalValidate = validateManifest; +let called = false; + +try { + validateManifest = function (...args) { + called = true; + return originalValidate(...args); + }; + + processManifest(_invalidManifests[0]); + + assert.isTrue( + called, + "processManifest should call validateManifest() when the manifest is invalid" + ); + +} catch (err) { + assert.fail(err); +} finally { + validateManifest = originalValidate; +} + +const spy = __helpers.spyOn(console, "log"); + +try { + processManifest(_testObj); + const expectedCalls = [ + ["Validation error: 1.51"], + [{ + containerId: "Invalid", + weight: "Invalid", + unit: "Missing", + hazmat: "Missing" + }] + ]; + assert.deepEqual(spy.calls, expectedCalls); + +} catch (err) { + assert.fail(err); +} finally { + spy.restore(); +} +``` + +# --seed-- + +## --seed-contents-- + +```js + +``` + +# --solutions-- + +```js +function normalizeUnits(manifest) { + const normalized = { + ...manifest + } + if (normalized.unit === "lb") { + normalized.weight = normalized.weight * 0.45; + normalized.unit = "kg"; + } + return normalized; +} + +function validateManifest(manifest) { + const result = {} + + if (manifest.containerId === undefined) { + result.containerId = "Missing"; + } else if ( + typeof manifest.containerId !== "number" || + !Number.isInteger(manifest.containerId) || + manifest.containerId <= 0 + ) { + result.containerId = "Invalid"; + } + if (manifest.destination === undefined) { + result.destination = "Missing"; + } else if (typeof manifest.destination !== "string" || manifest.destination.trim() === "") { + result.destination = "Invalid"; + } + if (manifest.weight === undefined) { + result.weight = "Missing"; + } else if (typeof manifest.weight !== "number" || manifest.weight <= 0 || Number.isNaN(manifest.weight)) { + result.weight = "Invalid"; + } + if (manifest.unit === undefined) { + result.unit = "Missing"; + } else if (manifest.unit !== "kg" && manifest.unit !== "lb") { + result.unit = "Invalid"; + } + if (manifest.hazmat === undefined) { + result.hazmat = "Missing"; + } else if (typeof manifest.hazmat !== "boolean") { + result.hazmat = "Invalid"; + } + + return result; +} + +function processManifest(manifest) { + const validation = validateManifest(manifest); + + if (Object.keys(validation).length > 0) { + console.log(`Validation error: ${manifest.containerId}`); + console.log(validation); + } else { + const normalized = normalizeUnits(manifest); + console.log(`Validation success: ${manifest.containerId}`); + console.log(`Total weight: ${normalized.weight} kg`); + } +} +``` diff --git a/curriculum/structure/blocks/lab-cargo-manifest-validator.json b/curriculum/structure/blocks/lab-cargo-manifest-validator.json new file mode 100644 index 00000000000..16cf7015ba3 --- /dev/null +++ b/curriculum/structure/blocks/lab-cargo-manifest-validator.json @@ -0,0 +1,14 @@ +{ + "isUpcomingChange": false, + "dashedName": "lab-cargo-manifest-validator", + "helpCategory": "JavaScript", + "blockLayout": "link", + "challengeOrder": [ + { + "id": "69a56b5069ca99f7317e6e19", + "title": "Build a Cargo Manifest Validator" + } + ], + "blockLabel": "lab", + "usesMultifileEditor": true +} diff --git a/curriculum/structure/superblocks/javascript-v9.json b/curriculum/structure/superblocks/javascript-v9.json index 4c8ff578df4..96e93e26c77 100644 --- a/curriculum/structure/superblocks/javascript-v9.json +++ b/curriculum/structure/superblocks/javascript-v9.json @@ -82,6 +82,7 @@ "dashedName": "javascript-objects", "blocks": [ "lecture-introduction-to-javascript-objects-and-their-properties", + "lab-cargo-manifest-validator", "lecture-working-with-json", "lecture-working-with-optional-chaining-and-object-destructuring", "workshop-recipe-tracker",