diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..4d82d31 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + "env": { + "node": true, + "browser": true, + "commonjs": true, + "es2021": true, + "jest": true + }, + "extends": "eslint:recommended", + "overrides": [ + ], + "parserOptions": { + "ecmaVersion": "latest" + }, + "rules": { + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6bba59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..4447379 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 BloomTech Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6904f71 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# Sprint 5 Challenge + +## Introduction + +Welcome to Sprint Challenge 5! Today, you'll practice using JavaScript to manipulate the DOM by fetching data from the network and building a section of a web page using "vanilla" JavaScript, without any frameworks. + +This experience will give you a taste of what your first task at a software company might feel like, and it matches the difficulty and scope of a take-home assignment during a hiring process for a Web Developer position. + +Here's an overview of the tasks you need to complete: + +1. **Obtain** JSON data from a web service. +1. **Combine** data obtained from different sources into a single data structure. +1. **Render** repeatable components to the DOM using the combined data. + +To succeed in this challenge, you'll need the following technical skills: + +1. **Promises** and the ability to deal with asynchronous code. +1. **Making HTTP requests** with Axios or fetch. +1. **Looping over data** to populate a new data structure. +1. **Selecting elements** and groups of elements from the DOM. +1. **Creating new elements** and attaching them to the DOM. +1. **Altering the text content** and class names of elements based on certain events. +1. **Adding simple interactivity** to elements via event listeners. + +Additionally, the following soft skills will greatly impact your performance: + +1. Attention to detail. Make sure there isn't a single character out of place! +1. Perseverance. Keep trying until you figure it out! +1. Patience. Make sure to read the entire README for important information. + +## Context + +On your first day as a junior web developer at a coding school, you have been assigned a ticket to complete. The task involves adding a new feature to the school's software platform. + +Specifically, you need to complete a page that displays a list of learners along with their basic information such as ID, name, email, and a list of mentors. Users should be able to click on a learner to highlight it, and the list of mentors for each learner should be expandable and collapsible. You can refer to the [full mockup](https://w-s5-challenge.herokuapp.com/) for the design and layout of the page. + +To help you complete the task, several members of your team will provide you with instructions and advice. + +### 💾 DevOps + +Below, your DevOps Engineer will help you set up your local environment and lauch the project. + +
+ Click to read + + --- + +This is a **full-stack web application** that comprises both back-end and front-end components. When deployed to production, the back-end part runs in the cloud (think Amazon Web Services), while the front-end is executed by the users' web browser (like Chrome for Android, or Firefox for desktop). + +As a front-end engineer, your focus is mainly on the files that load **on the user's device**. These files live inside the `frontent` folder in this particular project. The `backend` folder contains a web service built in Node, but the project as a whole is managed as a Node app, with a `package.json` file at the root containing meta-information and some useful commands developers can use to launch and test the application. + +1. You will **clone this repository** to your computer, which will allow you to run the software locally for development and testing purposes. + +1. You will navigate your terminal to the project folder **and execute `npm install`**. This will install the libraries listed inside `package.json`. Some of these packages are needed for the back-end to do its job of serving JSON data and also serving the front-end assets. Other libs help with things like testing and linting your code. + +1. After successful installation, you will run, in separate terminals, the two scripts listed inside `package.json`: **execute `npm start` in your first terminal, and `npm test` in your second**. On successful start, you will load the app in Chrome by **navigating the browser to `http://localhost:3001`**. The term "localhost" means "your machine", and the number is called a port, allowing multiple web servers to run on the same hardware, with one server per port. + +My job assisting you with local setup of the app is done! You will speak to our designer next. + + --- + +
+ +**[Watch the video](http://wistia.com)** + +### 🎨 Designer + +Below you will find information on how to approach the task. + +**[Watch video](http://wistia.com)** + +
+ Click to read important information + + --- + +Collaboration between a designer and a web developer can be very powerful. Designers excel at creating amazing user experiences and have a keen eye for beauty and usability, while developers are experts in the underlying technology of the product. + +However, it's important to remember that if a design for a feature exists, it's not a suggestion. Your job as a web developer is to implement the design with as much fidelity as possible. While a developer might think they have a better way to arrange elements on the screen, the mocks and designs are the result of research and hard work. It's important to treat them with the respect they deserve. + +For this project, there are certain constraints and requirements that must be followed, such as sticking to certain class names or keeping the structure of the HTML a certain way to avoid breaking the CSS. + +It's crucial to use the readable texts designed for the user interface verbatim. If a design reads "Loading Doughnuts...", then "Loading _Your_ Doughnuts..." is incorrect. Attention to detail is critical! + +Fortunately, you have a very detailed mock that you can load in your browser and inspect in detail, which will make your job much easier. And don't worry, you don't have to write any CSS because it's already been taken care of! + + --- + +
+ +### 🥷 Lead Developer + +Below, your team lead will discuss strategy and tactics for dealing with this ticket. + +
+ Click to read important information + + --- + +Hey there! Let's make sure you're up to speed on this project. + +- Have you installed the app on your computer and run both the `start` and `test` scripts? +- Have you studied the [mock](https://w-s5-challenge.herokuapp.com/) in the Elements tab of Dev Tools? +- Have you noticed how some text contents and some class names change as the user clicks around? + +Awesome! Our back-end engineer says that the data needed to build the Learner Cards comes from two endpoints: + +- [GET] http://localhost:3001/api/learners +- [GET] http://localhost:3001/api/mentors + +Make sure you have the `start` script running, or the endpoints won't work! You should try out the endpoints using Postman to see what they return. + +Here's the tricky part: each learner has a list of mentors, but the mentors are only identified by ID numbers. + +So, you need to fetch all mentors so you can match the mentor IDs from the `learners` endpoint with the names of the mentors from the `mentors` endpoint. To make things easier, use `Promise.all` to handle both requests. This will ensure that you don't start building the DOM until you have all the necessary data. + +When you have the data you need, combine the two lists into a data structure that you can work with. Ideally, it would look something like this: + +```js +[ + // etc + { + id: 22, + email:"mickey.mouse@example.com", + fullName: "Mickey Mouse", + mentors:['James Gosling', 'Mary Shaw'] + }, + // etc +] +``` + +Once you have the data in the right shape, you can create a component function that takes a learner in the format above, and returns a Learner Card. Then just loop over the data, generating cards as you go, and attaching them to the DOM. + +Make sure that each element you create uses the exact same class names and text contents as those in the mock! Also, render the cards in the same order as they come from the learner endpoint. + +As for interactivity, all the behaviors on the page as the user clicks on the cards are governed by changes in text contents of some elements, and changes to some class names on only two elements. + +Don't use any class names that aren't in the mock. That would break the CSS. Also, don't add any styles or HTML. And only make changes to `frontend/index.js`! + +It might seem like you need several click handlers on different elements, but that would just make the code more complicated. Remember, events bubble up from the target to its ancestor elements! So it's easier to just slap an event listener on the card element, and then check who's the target of the click before taking the appropriate action. + + --- + +
+ +**[Watch video](http://wistia.com)** + +## Introduction + +Submit your assignment to Codegrade. diff --git a/backend/data.js b/backend/data.js new file mode 100644 index 0000000..d4acdea --- /dev/null +++ b/backend/data.js @@ -0,0 +1,201 @@ +const learners = [ + { + "id": 6, + "fullName": "Bob Johnson", + "email": "bob.johnson@example.com", + "mentors": [78, 17] + }, + { + "id": 52, + "fullName": "Samantha Richards", + "email": "samantha.richards@example.com", + "mentors": [12, 83] + }, + { + "id": 84, + "fullName": "Harry Potter", + "email": "harry.potter@example.com", + "mentors": [71, 95] + }, + { + "id": 18, + "fullName": "Gina Smith", + "email": "gina.smith@example.com", + "mentors": [32] + }, + { + "id": 77, + "fullName": "Max Power", + "email": "max.power@example.com", + "mentors": [51, 94] + }, + { + "id": 68, + "fullName": "Daisy Duke", + "email": "daisy.duke@example.com", + "mentors": [58, 83, 49] + }, + { + "id": 1, + "fullName": "Jack Sparrow", + "email": "jack.sparrow@example.com", + "mentors": [12, 67] + }, + { + "id": 48, + "fullName": "Homer Simpson", + "email": "homer.simpson@example.com", + "mentors": [42] + }, + { + "id": 97, + "fullName": "Luna Lovegood", + "email": "luna.lovegood@example.com", + "mentors": [12, 17, 25, 58] + }, + { + "id": 3, + "fullName": "Joe Bloggs", + "email": "joe.bloggs@example.com", + "mentors": [83] + }, + { + "id": 35, + "fullName": "Bilbo Baggins", + "email": "bilbo.baggins@example.com", + "mentors": [51, 60, 95] + }, + { + "id": 29, + "fullName": "Marge Simpson", + "email": "marge.simpson@example.com", + "mentors": [78, 14] + }, + { + "id": 8, + "fullName": "Peter Parker", + "email": "peter.parker@example.com", + "mentors": [51, 83, 88] + }, + { + "id": 57, + "fullName": "Betty Boop", + "email": "betty.boop@example.com", + "mentors": [17, 71, 42] + }, + { + "id": 22, + "fullName": "Mickey Mouse", + "email": "mickey.mouse@example.com", + "mentors": [83] + }, + { + "id": 91, + "fullName": "Daffy Duck", + "email": "daffy.duck@example.com", + "mentors": [63, 71] + } +] + +const mentors = [ + { + "id": 12, + "firstName": "Ada", + "lastName": "Lovelace" + }, + { + "id": 78, + "firstName": "Bill", + "lastName": "Gates" + }, + { + "id": 63, + "firstName": "Brendan", + "lastName": "Eich" + }, + { + "id": 42, + "firstName": "Brian", + "lastName": "Kernighan" + }, + { + "id": 94, + "firstName": "Dan", + "lastName": "Ingalls" + }, + { + "id": 17, + "firstName": "Grace", + "lastName": "Hopper" + }, + { + "id": 7, + "firstName": "Guido", + "lastName": "van Rossum" + }, + { + "id": 83, + "firstName": "James", + "lastName": "Gosling" + }, + { + "id": 51, + "firstName": "Linus", + "lastName": "Torvalds" + }, + { + "id": 67, + "firstName": "Margaret", + "lastName": "Hamilton" + }, + { + "id": 60, + "firstName": "Mark", + "lastName": "Zuckerberg" + }, + { + "id": 25, + "firstName": "Martin", + "lastName": "Fowler" + }, + { + "id": 88, + "firstName": "Mary", + "lastName": "Shaw" + }, + { + "id": 71, + "firstName": "Mitchell", + "lastName": "Hashimoto" + }, + { + "id": 95, + "firstName": "Rasmus", + "lastName": "Lerdorf" + }, + { + "id": 14, + "firstName": "Robert", + "lastName": "Martin" + }, + { + "id": 32, + "firstName": "Sergey", + "lastName": "Brin" + }, + { + "id": 49, + "firstName": "Sheryl", + "lastName": "Sandberg" + }, + { + "id": 58, + "firstName": "Yukihiro", + "lastName": "Matsumoto" + } +] + +module.exports = { + mentors, + learners, +} diff --git a/backend/mock.js b/backend/mock.js new file mode 100644 index 0000000..8ca088c --- /dev/null +++ b/backend/mock.js @@ -0,0 +1,26 @@ +const { rest } = require('msw') +const { setupServer } = require('msw/node') +const { learners, mentors } = require('./data') + +function getLearners(req, res, ctx) { + return res( + ctx.json(learners), + ) +} + +function getMentors(req, res, ctx) { + return res( + ctx.json(mentors), + ) +} + +const handlers = [ + rest.get('http://localhost:3001/api/learners', getLearners), + rest.get('http://localhost:3001/api/mentors', getMentors), + rest.get('/api/learners', getLearners), + rest.get('/api/mentors', getMentors), +] + +module.exports = { + server: setupServer(...handlers), +} diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..33a4bb6 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,57 @@ +const express = require('express') +const path = require('path') +const { learners, mentors } = require('./data') + +const delay = 500 +const server = express() +const port = process.env.PORT || 3001 + +server.use(express.json()) +server.use(express.static(path.join(__dirname, '../frontend'))) + +server.get('/api/mentors', async (req, res, next) => { + try { + setTimeout(() => { + res.json(mentors) + }, delay); + } catch (err) { + next(err) + } +}) +server.get('/api/learners', async (req, res, next) => { + try { + setTimeout(() => { + res.json(learners) + }, delay) + } catch (err) { + next(err) + } +}) + +// SPA +server.get('*', (req, res) => { + res.sendFile(path.join(__dirname, '../frontend/index.html')) +}) +// 404 +server.use((req, res) => { + res.status(404).json({ + message: `Endpoint [${req.method}] ${req.originalUrl} does not exist`, + }) +}) +// ERR +server.use((err, req, res, next) => { // eslint-disable-line + res.status(err.status || 500).json({ + message: err.message, + stack: err.stack, + }) +}) + +server.listen(port, () => { + console.log(` +------------------------------------------------------------------------- +See YOUR PROJECT in the browser, navigating to --> http://localhost:3001 + +See the live mock navigating to --> https://w-s5-challenge.herokuapp.com/ +------------------------------------------------------------------------- +`) +}) diff --git a/frontend/TitilliumWeb.ttf b/frontend/TitilliumWeb.ttf new file mode 100755 index 0000000..49109f8 Binary files /dev/null and b/frontend/TitilliumWeb.ttf differ diff --git a/frontend/favicon.ico b/frontend/favicon.ico new file mode 100644 index 0000000..14dbe08 Binary files /dev/null and b/frontend/favicon.ico differ diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..78aa773 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,32 @@ + + + + + + + Sprint 5 Challenge Submission + + + + + + + +
+

Sprint 5 Challenge Submission

+

Learner Cards

+

Fetching learner cards...

+
+
+
+ +
+
+ + + + diff --git a/frontend/index.js b/frontend/index.js new file mode 100644 index 0000000..40e98cb --- /dev/null +++ b/frontend/index.js @@ -0,0 +1,18 @@ +async function sprintChallenge5() { + const footer = document.querySelector('footer') + const currentYear = new Date().getFullYear() + footer.textContent = `© BLOOM INSTITUTE OF TECHNOLOGY ${currentYear}` + + // 👇 WORK WORK BELOW THIS LINE + + + // 👆 WORK WORK ABOVE THIS LINE +} + +// ❗ DO NOT CHANGE THE CODE BELOW +sprintChallenge5() +// This try/catch is needed to expose the sprintChallenge5 function to the test runner +try { module.exports = { sprintChallenge5 } } catch { + // module.exports does not exist in the browser + // so this is always a crash in Chrome +} diff --git a/frontend/reset.css b/frontend/reset.css new file mode 100644 index 0000000..14a1e44 --- /dev/null +++ b/frontend/reset.css @@ -0,0 +1,55 @@ +/* DO NOT CHANGE THIS FILE */ +/* DO NOT CHANGE THIS FILE */ +/* DO NOT CHANGE THIS FILE */ + +/* RESET BY https://www.joshwcomeau.com/css/custom-css-reset */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; +} + +html, +body { + height: 100%; +} + +body { + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +input, +button, +textarea, +select { + font: inherit; +} + +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +#root, +#__next { + isolation: isolate; +} diff --git a/frontend/styles.css b/frontend/styles.css new file mode 100644 index 0000000..8951570 --- /dev/null +++ b/frontend/styles.css @@ -0,0 +1,104 @@ +/* DO NOT CHANGE THIS FILE */ +/* DO NOT CHANGE THIS FILE */ +/* DO NOT CHANGE THIS FILE */ +@font-face { + font-family: 'Titillium Web'; + src: url('./TitilliumWeb.ttf'); +} + +body { + font-family: 'Titillium Web', sans-serif; + border: 1rem solid #ff4b00; +} + +body, +section { + display: flex; + flex-direction: column; + align-items: center; +} + +h1, +h2 { + margin-bottom: 1.2rem; +} + +h4 { + margin-top: 0.4rem; + cursor: pointer; +} + +header { + padding: 1rem; + text-align: center; +} + +header .info { + font-size: 1.3rem; + font-style: italic; +} + +section { + width: 100%; + overflow-y: auto; + border-top: 1px solid grey; + border-bottom: 1px solid grey; +} + +.card { + color: gray; + padding: 1rem; + opacity: 0.6; + margin: 0.8rem; + border: 1px solid rgb(93, 93, 93); + border-radius: 5px; + background-color: rgba(233, 223, 230, 0.75); + transition: background-color 0.3s ease; +} + +.card:first-child { + margin-top: 2rem; +} + +.card ul { + list-style-type: square; + padding-top: 0.4rem; +} + +.card h3 { + font-size: 1em; + transition: font-size 0.1s ease; + margin-bottom: 0.8rem; +} + +.card h4.closed~ul { + display: none; +} + +.card.selected { + color: black; + background-color: white; + box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; +} + +.card.selected h3 { + transition: font-size 0.1s ease; + font-size: 1.07em; +} + +.card h4.closed::before { + content: '▶ '; +} + +.card h4.open::before { + content: '▼ '; +} + +.card.selected h4.closed::before, +.card.selected h4.open::before { + color: #00808c; +} + +footer { + padding: 1rem; +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..51dfdd4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,229 @@ +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 1, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/96/mtz831_d14j85kmn9jcn1mc40000gn/T/jest_dx", + + // Automatically clear mock calls, instances, contexts and results before every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + // coverageDirectory: undefined, + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + "setupFiles": [ + "./jest.globals.js" + ], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "jsdom", + + // Options that will be passed to the testEnvironment + testEnvironmentOptions: { + resources: 'usable', + runScripts: 'dangerously', + html: ` + + + + + Sprint 5 Challenge Submission + + + + +
+

Sprint 5 Challenge Submission

+

Learner Cards

+

Fetching learner cards...

+
+
+
+ +
+
+ + + + +` + }, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/jest.globals.js b/jest.globals.js new file mode 100644 index 0000000..b3f226c --- /dev/null +++ b/jest.globals.js @@ -0,0 +1,8 @@ +// This makes fetch and axios work in the tests +const axios = require('axios') +const nodeFetch = require('node-fetch') + +globalThis.axios = axios +globalThis.fetch = nodeFetch +globalThis.Request = nodeFetch.Request +globalThis.Response = nodeFetch.Response diff --git a/mvp.test.js b/mvp.test.js new file mode 100644 index 0000000..308ed8a --- /dev/null +++ b/mvp.test.js @@ -0,0 +1,247 @@ +const { screen, fireEvent, within } = require('@testing-library/dom') +require('@testing-library/jest-dom') +const { version } = require('./package.json') +const { server } = require('./backend/mock') +const { learners, mentors } = require('./backend/data') +const { sprintChallenge5 } = require('./frontend/index') + +const waitForOptions = { timeout: 150 } // so Codegrade does not take forever + +beforeAll(() => { server.listen() }) +afterAll(() => { server.close() }) +beforeEach(async () => { await sprintChallenge5() }) +afterEach(() => { + document.querySelector('body').innerHTML = ` +
+

Sprint 5 Challenge Submission

+

Learner Cards

+

Fetching learner cards...

+
+
+
+ +
+
+ +` +}) + +async function firstCardRender() { + const bob = await screen.findByText('Bob Johnson', {}, waitForOptions) + expect(bob).toBeInTheDocument() +} + +describe('Sprint Challenge 5', () => { + describe('Sprint setup', () => { + test('👉 [1] Version of challenge is valid', () => { + const versions = ['1.0.0'] + expect(versions.indexOf(version)).toBeGreaterThan(-1) + }) + test('👉 [2] The sprintChallenge5 function does not crash', async () => { + await expect(sprintChallenge5()).resolves.not.toThrowError() + }) + test('👉 [3] The code inside the script is clean', () => { + expect(window.aidgfuioghausfdu).not.toBeDefined() + }) + }) + describe('Initial HTML', () => { + test('👉 [4]

text is "Sprint 5 Challenge Submission"', () => { + screen.getByText('Sprint 5 Challenge Submission') + }) + test('👉 [5]

text is "Learner Cards"', () => { + screen.getByText('Learner Cards') + }) + test('👉 [6]

text is "Fetching learner cards..."', () => { + screen.getByText('Fetching learner cards...') + }) + test('👉 [7]