39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/utils/machineUtils.ts:
--------------------------------------------------------------------------------
1 | import { find, findIndex, get, gte } from "lodash/fp"
2 |
3 | export const getAllLessons = (coursesJson: object, lessonPath) => {
4 | const [sectionSlug] = lessonPath.split("/")
5 | const course = getCourse(coursesJson, sectionSlug)
6 | return get("lessons", course)
7 | }
8 |
9 | export const findLesson = (coursesJson: object, lessonPath: string) => {
10 | const [, lessonSlug] = lessonPath.split("/")
11 | const lessons = getAllLessons(coursesJson, lessonPath)
12 | return find({ slug: lessonSlug }, lessons)
13 | }
14 |
15 | export const getChallenge = (
16 | coursesJson: object,
17 | lessonPath: string,
18 | challengeIndex: number
19 | ) => {
20 | const lesson = findLesson(coursesJson, lessonPath)
21 | return lesson.challenges[challengeIndex]
22 | }
23 |
24 | export const getLessonIndex = (coursesJson: object, lessonPath: string) => {
25 | const [, lessonSlug] = lessonPath.split("/")
26 | const lessons = getAllLessons(coursesJson, lessonPath)
27 | return findIndex({ slug: lessonSlug }, lessons)
28 | }
29 |
30 | export const getCourse = (coursesJson: object, lessonPath: string) => {
31 | const [sectionSlug] = lessonPath.split("/")
32 | return coursesJson[sectionSlug]
33 | }
34 |
35 | export const isLessonCompleted = (progressService, lessonPath) => {
36 | return gte(
37 | findIndex(
38 | { id: lessonPath, status: "completed" },
39 | progressService.state.context.lessons
40 | ),
41 | 0
42 | )
43 | }
44 |
45 | export const isSectionCompleted = (sectionsCompleted, sectionSlug) => {
46 | return sectionsCompleted.includes(sectionSlug)
47 | }
48 |
--------------------------------------------------------------------------------
/content/courses/testing-foundations/testing-is-a-mindset.mdx:
--------------------------------------------------------------------------------
1 | # Testing is a Mindset
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
4 |
5 | ## Section 1
6 |
7 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
8 |
9 | ## Section 2
10 |
11 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start",
7 | "lint": "next lint"
8 | },
9 | "dependencies": {
10 | "@headlessui/react": "^1.5.0",
11 | "@heroicons/react": "^1.0.6",
12 | "@mapbox/rehype-prism": "^0.8.0",
13 | "@tailwindcss/aspect-ratio": "^0.4.0",
14 | "@tailwindcss/forms": "^0.5.0",
15 | "@tailwindcss/typography": "^0.5.2",
16 | "@types/glob": "^7.2.0",
17 | "@xstate/react": "^2.0.1",
18 | "dotenv": "^16.0.0",
19 | "glob": "^7.2.0",
20 | "globby": "^13.1.1",
21 | "linkinator": "^3.0.3",
22 | "next": "^12.3.4",
23 | "react": "^17.0.2",
24 | "react-dom": "^17.0.2",
25 | "react-hook-form": "^7.27.1",
26 | "react-slick": "^0.28.1",
27 | "rehype-slug": "^5.0.1",
28 | "xstate": "^4.30.5"
29 | },
30 | "devDependencies": {
31 | "@types/chai": "^4.3.0",
32 | "@types/lodash": "^4.14.179",
33 | "@types/mocha": "^9.1.0",
34 | "@types/react": "^17.0.39",
35 | "autoprefixer": "^10.4.2",
36 | "chai": "^4.3.6",
37 | "eslint": "^8.10.0",
38 | "eslint-config-next": "^12.1.0",
39 | "eslint-config-prettier": "^8.5.0",
40 | "eslint-plugin-prettier": "^4.0.0",
41 | "gray-matter": "^4.0.3",
42 | "markdown-toc-unlazy": "^1.0.1",
43 | "mocha": "^9.2.1",
44 | "next-mdx-remote": "^4.0.0",
45 | "next-remote-watch": "^1.0.0",
46 | "next-sitemap": "^2.5.7",
47 | "postcss": "^8.4.8",
48 | "prettier": "^2.5.1",
49 | "pretty-quick": "^3.1.3",
50 | "slugify": "^1.6.5",
51 | "tailwindcss": "^3.0.23",
52 | "tsconfig-paths": "^3.13.0",
53 | "typescript": "^4.6.2",
54 | "unslugify": "^1.0.2",
55 | "url-loader": "^4.1.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pages/[course]/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head"
2 | import Layout from "../../components/Layout"
3 | import CourseHero from "../../components/Course/CourseHero"
4 | import CourseContent from "../../components/Course/CourseContent"
5 | import { progressService } from "../../machines/progressService"
6 | import { fetchCourses } from "../../lib/fetch-courses"
7 |
8 | export default function SectionPage({
9 | title,
10 | lessons,
11 | description,
12 | learnFeatures,
13 | content,
14 | courses,
15 | course,
16 | }) {
17 | return (
18 |
23 |
24 | {title} | Testing Next.js Applications with Cypress
25 |
26 |
27 |
28 |
33 |
40 |
41 | )
42 | }
43 |
44 | export async function getStaticProps({ params }) {
45 | const coursesJson = await fetchCourses()
46 | const { title, lessons, description, learnFeatures } =
47 | coursesJson[params.course]
48 | const courses = Object.keys(coursesJson)
49 |
50 | return {
51 | props: {
52 | lessons,
53 | title,
54 | description,
55 | learnFeatures,
56 | content: coursesJson,
57 | courses,
58 | course: params.course,
59 | },
60 | }
61 | }
62 |
63 | export async function getStaticPaths() {
64 | const coursesJson = await fetchCourses()
65 | const courses = Object.keys(coursesJson)
66 | const paths = courses.map((course) => {
67 | const { title, lessons } = coursesJson[course]
68 | return { params: { course, lessons, title } }
69 | })
70 |
71 | return {
72 | paths,
73 | fallback: false,
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/components/Lesson/LessonProgress.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from "next/dynamic"
2 | import Link from "next/link"
3 | import { isLessonCompleted } from "../../utils/machineUtils"
4 |
5 | const ProgressLine = dynamic(() => import("../Progress/ProgressLine"), {
6 | ssr: false,
7 | })
8 |
9 | const CompletedLesson = dynamic(() => import("../Progress/CompletedLesson"), {
10 | ssr: false,
11 | })
12 |
13 | const IncompleteLesson = dynamic(() => import("../Progress/IncompleteLesson"), {
14 | ssr: false,
15 | })
16 |
17 | function classNames(...classes) {
18 | return classes.filter(Boolean).join(" ")
19 | }
20 |
21 | export default function LessonProgress({ lessons, progressService, course }) {
22 | return (
23 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/components/Home/HomeProgress.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from "next/dynamic"
2 | import Link from "next/link"
3 | import { isLessonCompleted } from "../../utils/machineUtils"
4 |
5 | const ProgressLine = dynamic(() => import("../Progress/ProgressLine"), {
6 | ssr: false,
7 | })
8 |
9 | const CompletedLesson = dynamic(() => import("../Progress/CompletedLesson"), {
10 | ssr: false,
11 | })
12 |
13 | const IncompleteLesson = dynamic(() => import("../Progress/IncompleteLesson"), {
14 | ssr: false,
15 | })
16 |
17 | function classNames(...classes) {
18 | return classes.filter(Boolean).join(" ")
19 | }
20 |
21 | export default function HomeProgress({ course, content, progressService }) {
22 | return (
23 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/components/Course/CourseProgress.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from "next/dynamic"
2 | import Link from "next/link"
3 | import { isLessonCompleted } from "../../utils/machineUtils"
4 |
5 | const ProgressLine = dynamic(() => import("../Progress/ProgressLine"), {
6 | ssr: false,
7 | })
8 |
9 | const CompletedLesson = dynamic(() => import("../Progress/CompletedLesson"), {
10 | ssr: false,
11 | })
12 |
13 | const IncompleteLesson = dynamic(() => import("../Progress/IncompleteLesson"), {
14 | ssr: false,
15 | })
16 |
17 | function classNames(...classes) {
18 | return classes.filter(Boolean).join(" ")
19 | }
20 |
21 | export default function CourseProgress({ lessons, progressService, course }) {
22 | return (
23 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "baseBranch": "main",
4 | "automerge": false,
5 | "commitMessage": "{{semanticPrefix}}Update {{depName}} to {{newVersion}} 🌟",
6 | "prTitle": "{{semanticPrefix}}{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isRange}}{{newVersion}}{{else}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}{{/if}} 🌟",
7 | "major": {
8 | "automerge": false
9 | },
10 | "minor": {
11 | "automerge": false
12 | },
13 | "prHourlyLimit": 1,
14 | "updateNotScheduled": false,
15 | "timezone": "America/New_York",
16 | "masterIssue": true,
17 | "schedule": ["every weekend"],
18 | "packageRules": [
19 | {
20 | "packageNames": ["cypress"],
21 | "schedule": ["every weekday"]
22 | },
23 | {
24 | "groupName": "automergeTypesMinor",
25 | "automerge": true,
26 | "major": { "automerge": false },
27 | "matchPackagePatterns": ["^@types/"]
28 | },
29 | {
30 | "groupName": "devDeps",
31 | "description": "automerge minor updates of widely used libraries in devDeps",
32 | "automerge": true,
33 | "matchUpdateTypes": ["minor"],
34 | "matchDepTypes": ["devDependencies"]
35 | },
36 | {
37 | "groupName": "Typescript",
38 | "matchPackageNames": ["typescript", "ts-node", "tsconfig-paths"]
39 | },
40 | {
41 | "groupName": "React",
42 | "matchPackageNames": ["react", "react-dom"]
43 | },
44 | {
45 | "groupName": "Tailwind",
46 | "matchPackagePatterns": [
47 | "@headlessui/react",
48 | "@heroicons/react",
49 | "tailwindcss",
50 | "postcss",
51 | "autoprefixer"
52 | ]
53 | },
54 | {
55 | "groupName": "Next",
56 | "matchPackageNames": ["next"],
57 | "matchPackagePatterns": ["^next-", "^@zeit/next-"]
58 | },
59 | {
60 | "groupName": "Xstate",
61 | "matchPackageNames": ["xstate", "@xstate/react"]
62 | },
63 | {
64 | "groupName": "ESLint and Prettier",
65 | "matchPackageNames": ["eslint", "prettier"],
66 | "matchPackagePatterns": ["^eslint-config-", "^eslint-plugin-"]
67 | },
68 | {
69 | "groupName": "Misc FE libs",
70 | "matchPackageNames": ["gray-matter", "slugify", "markdown-toc-unlazy"]
71 | },
72 | {
73 | "groupName": "Misc dev tooling",
74 | "matchPackageNames": ["husky", "mocha", "chai", "pretty-quick"]
75 | }
76 | ]
77 | }
78 |
--------------------------------------------------------------------------------
/content/courses/cypress-fundamentals/cypress-runs-in-the-browser.mdx:
--------------------------------------------------------------------------------
1 | # Cypress runs in the browser
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
4 |
5 | ## Section 1
6 |
7 | ```js
8 | describe("Next Lesson Button on Course Pages", () => {
9 | beforeEach(() => {
10 | // @ts-ignore
11 | cy.restoreLocalStorage()
12 | })
13 |
14 | afterEach(() => {
15 | // @ts-ignore
16 | cy.saveLocalStorage()
17 | })
18 |
19 | it("says 'Start Course' and links to the first lesson if none of the lessons have been completed", () => {
20 | cy.visit(`/${sectionSlug}`)
21 | cy.getBySel("next-lesson-button").then(($btn) => {
22 | // @ts-ignore
23 | const text = $btn.text()
24 | // @ts-ignore
25 | const href = $btn.attr("href")
26 |
27 | expect(text).to.eq("Start Course")
28 | expect(href).to.eq(
29 | `/${sectionSlug}/${coursesJson[`${sectionSlug}`].lessons[0].slug}`
30 | )
31 | })
32 | })
33 | ```
34 |
35 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
36 |
37 | ## Section 2
38 |
39 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
40 |
--------------------------------------------------------------------------------
/content/courses/cypress-fundamentals/command-chaining.mdx:
--------------------------------------------------------------------------------
1 | # Command Chaining
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
4 |
5 | ## Section 1
6 |
7 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
8 |
9 | ## Section 2
10 |
11 | ```js
12 | export async function getStaticProps(context) {
13 | const res = await fetch(`https://.../data`)
14 | const data = await res.json()
15 |
16 | if (!data) {
17 | return {
18 | notFound: true,
19 | }
20 | }
21 |
22 | return {
23 | props: { data }, // will be passed to the page component as props
24 | }
25 | }
26 | ```
27 |
28 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
29 |
30 | ## Section 3
31 |
32 | Aenean eu lobortis nisi. Donec rutrum fringilla orci, vitae fermentum nisi porta eu. Nam sit amet odio lacus. Nulla eu fermentum metus. Suspendisse eu aliquet lectus, at fringilla massa. Pellentesque hendrerit iaculis metus id aliquet. Curabitur urna libero, molestie in lorem porta, imperdiet ultricies ex. Cras elementum aliquam lorem, non semper enim fermentum sed. Nullam egestas, elit at vulputate pretium, dui enim ornare erat, a tristique nulla risus id neque.
33 |
--------------------------------------------------------------------------------
/content/courses/testing-your-first-application/installing-cypress-and-writing-our-first-test.mdx:
--------------------------------------------------------------------------------
1 | # Installing Cypress and writing our first test
2 |
3 | ```js
4 | describe("Next Lesson Button on Course Pages", () => {
5 | beforeEach(() => {
6 | // @ts-ignore
7 | cy.restoreLocalStorage()
8 | })
9 |
10 | afterEach(() => {
11 | // @ts-ignore
12 | cy.saveLocalStorage()
13 | })
14 |
15 | it("says 'Start Course' and links to the first lesson if none of the lessons have been completed", () => {
16 | cy.visit(`/${sectionSlug}`)
17 | cy.getBySel("next-lesson-button").then(($btn) => {
18 | // @ts-ignore
19 | const text = $btn.text()
20 | // @ts-ignore
21 | const href = $btn.attr("href")
22 |
23 | expect(text).to.eq("Start Course")
24 | expect(href).to.eq(
25 | `/${sectionSlug}/${coursesJson[`${sectionSlug}`].lessons[0].slug}`
26 | )
27 | })
28 | })
29 | ```
30 |
31 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
32 |
33 | ## Section 1
34 |
35 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
36 |
37 | ## Section 2
38 |
39 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
40 |
--------------------------------------------------------------------------------
/components/Subscribe.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useForm, SubmitHandler } from "react-hook-form"
3 |
4 | type Inputs = {
5 | email: string
6 | }
7 |
8 | export default function Subscribe() {
9 | const {
10 | register,
11 | handleSubmit,
12 | formState,
13 | formState: { errors },
14 | } = useForm()
15 |
16 | const [isSubmitted, setIsSubmitted] = React.useState("")
17 |
18 | const onSubmit: SubmitHandler = async (data, event) => {
19 | event.target.reset()
20 |
21 | const subscribe = await fetch("/api/subscribe", {
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | },
26 | body: JSON.stringify(data),
27 | })
28 | const response = await subscribe.json()
29 |
30 | setIsSubmitted(response.message)
31 | }
32 |
33 | return (
34 |
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/docs/pages/index.md:
--------------------------------------------------------------------------------
1 | # Pages
2 |
3 | All of the pages in the application and how Next.js uses some of them to generate [dynamic routes](https://nextjs.org/docs/routing/dynamic-routes).
4 |
5 | ## index.tsx
6 |
7 | This is the index/home page of the app, i.e., `/`
8 |
9 | ## \_app.tsx
10 |
11 | This page acts like a "layout" page and wraps all of the other pages within the app
12 |
13 | ```js{11}
14 | function MyApp({ Component, pageProps }) {
15 | return (
16 | <>
17 |
18 |
19 |
23 |
24 |
25 | >
26 | )
27 | }
28 | ```
29 |
30 | The `` on line `11` is where the other pages are passed into this special component and rendered.
31 |
32 | ## [section]/
33 |
34 | This is a special directory that Next.js uses for [dynamic routes](https://nextjs.org/docs/routing/dynamic-routes). There are two pages within this directory.
35 |
36 | 1. `index.tsx` - is reponsible for rendering all of the course landing pages, i.e.,
37 |
38 | - `/testing-your-first-application`.
39 | - `/testing-foundations`.
40 | - `/cypress-fundamentals`.
41 | - `/advanced-cypress-concepts`.
42 |
43 | 2. `[slug].tsx` - is a [dynamic page](https://nextjs.org/docs/basic-features/pages) and is responsible for rendering all of the lesson pages for the courses, i.e., `/testing-your-first-application/todomvc-app-install-and-overview`.
44 |
45 | ## real-world-examples/
46 |
47 | This is a special directory that Next.js uses for [dynamic routes](https://nextjs.org/docs/routing/dynamic-routes). There are two pages within this directory.
48 |
49 | 1. `index.tsx` - is reponsible for rendering the real world examples landing page, i.e.,
50 |
51 | - `/real-world-examples`.
52 |
53 | 2. `[slug].tsx` - is a [dynamic page](https://nextjs.org/docs/basic-features/pages) and is responsible for rendering all of the real world example pages, i.e., `/real-world-examples/authentication-overview-and-setup`.
54 |
55 | ## getStaticProps()
56 |
57 | [getStaticProps()](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) is a special method from Next.js that is used to fetch the data necessary to render the page and its components.
58 |
59 | ## getStaticPaths()
60 |
61 | [getStaticPaths()](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation) is a special method from Next.js that is used to get the lists of paths necessary to render dynamic routes.
62 |
--------------------------------------------------------------------------------
/components/Lesson/LessonBreadcrumbs.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 | import { HomeIcon } from "@heroicons/react/solid"
3 |
4 | const pages = [
5 | { name: "Projects", href: "#", current: false },
6 | { name: "Project Nero", href: "#", current: true },
7 | ]
8 |
9 | export default function Breadcrumbs({ lessonPath, sectionTitle, lessonData }) {
10 | const [sectionSlug] = lessonPath.split("/")
11 |
12 | return (
13 |
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/machines/progressMachine.ts:
--------------------------------------------------------------------------------
1 | import { createMachine, assign } from "xstate"
2 | import { ProgressContext } from "common"
3 | import { concat } from "lodash/fp"
4 | import {
5 | getCourse,
6 | getChallenge,
7 | isSectionCompleted,
8 | } from "../utils/machineUtils"
9 | import coursesJson from "../data/courses.json"
10 |
11 | const defaultContext: ProgressContext = {
12 | sectionsCompleted: [],
13 | lessons: [],
14 | disableChallenges: false,
15 | }
16 |
17 | export const progressMachine = createMachine(
18 | {
19 | id: "progress",
20 | initial: "started",
21 | context: defaultContext,
22 | states: {
23 | started: {
24 | on: {
25 | SKIP_ANSWER: {
26 | actions: ["saveProgress"],
27 | },
28 | SUBMIT_ANSWER: {
29 | actions: ["validateAndLogAnswer", "isSectionCompleted"],
30 | },
31 | DISABLE_CHALLENGES: { actions: ["disableChallenges"] },
32 | COMPLETE_LESSON: { actions: ["saveProgress"] },
33 | },
34 | },
35 | completed: {
36 | on: {
37 | CLEAR_All_PROGRESS: "",
38 | },
39 | },
40 | },
41 | },
42 | {
43 | actions: {
44 | saveProgress: assign((context: any, event: any) => ({
45 | lessons: concat(context.lessons, {
46 | id: event.id,
47 | status: "completed",
48 | }),
49 | })),
50 | validateAndLogAnswer: assign((context: any, event: any) => {
51 | const challenge = getChallenge(
52 | coursesJson,
53 | event.id,
54 | event.challengeIndex
55 | )
56 |
57 | const isCorrectMultipleChoiceAnswer =
58 | challenge.challengeType === "multiple-choice" &&
59 | challenge.correctAnswerIndex === event.userAnswerIndex
60 |
61 | if (isCorrectMultipleChoiceAnswer) {
62 | return {
63 | lessons: concat(context.lessons, {
64 | id: event.id,
65 | status: "completed",
66 | }),
67 | }
68 | }
69 | }),
70 | disableChallenges: assign((context: any, event: any) => ({
71 | disableChallenges: event.value,
72 | })),
73 |
74 | isSectionCompleted: assign((context: any, event: any) => {
75 | const [sectionSlug] = event.id.split("/")
76 | const course = getCourse(coursesJson, event.id)
77 | const completedLessons = context.lessons.filter(
78 | (lesson) => lesson.status === "completed"
79 | )
80 |
81 | if (
82 | completedLessons.length === course.lessons.length &&
83 | !isSectionCompleted(context.sectionsCompleted, sectionSlug)
84 | ) {
85 | return {
86 | sectionsCompleted: concat(context.sectionsCompleted, sectionSlug),
87 | }
88 | }
89 | }),
90 | },
91 | }
92 | )
93 |
--------------------------------------------------------------------------------
/components/Home/HomeFeatures.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | LightBulbIcon,
3 | BeakerIcon,
4 | TerminalIcon,
5 | DatabaseIcon,
6 | CodeIcon,
7 | AdjustmentsIcon,
8 | } from "@heroicons/react/outline"
9 |
10 | const features = [
11 | {
12 | name: "Prepare your Testing Mindset",
13 | icon: LightBulbIcon,
14 | description: "lorem ipsum dolor sit amet, consectetur adipiscing elit.",
15 | },
16 | {
17 | name: "Learn What to Test",
18 | icon: BeakerIcon,
19 | description: "lorem ipsum dolor sit amet, consectetur adipiscing elit.",
20 | },
21 | {
22 | name: "Debug Failing Tests Efficiently",
23 | icon: TerminalIcon,
24 | description: "lorem ipsum dolor sit amet, consectetur adipiscing elit.",
25 | },
26 | {
27 | name: "Learn Database Initialization & Seeding",
28 | icon: DatabaseIcon,
29 | description: "lorem ipsum dolor sit amet, consectetur adipiscing elit.",
30 | },
31 | {
32 | name: "Understand Different Testing Types",
33 | icon: CodeIcon,
34 | description: "lorem ipsum dolor sit amet, consectetur adipiscing elit.",
35 | },
36 | {
37 | name: "Apply your Knowledge",
38 | icon: AdjustmentsIcon,
39 | description: "lorem ipsum dolor sit amet, consectetur adipiscing elit.",
40 | },
41 | ]
42 |
43 | export default function HomeFeatures() {
44 | return (
45 |
46 |
47 |
48 | What you'll learn
49 |
50 |
51 |
52 | {features.map((feature) => (
53 |
54 |
55 |
56 |
57 |
58 |
62 |
63 |
64 |
65 | {feature.name}
66 |
67 |
68 | {feature.description}
69 |
70 |
71 |
72 |
73 | ))}
74 |
75 |
76 |
77 |
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/content/courses/testing-foundations/knowing-what-to-test.mdx:
--------------------------------------------------------------------------------
1 | # Knowing What to Test
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
4 |
5 | ## Section 1
6 |
7 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
8 |
9 | ## Section 2
10 |
11 | ```js
12 | export async function getStaticProps(context) {
13 | const res = await fetch(`https://.../data`)
14 | const data = await res.json()
15 |
16 | if (!data) {
17 | return {
18 | notFound: true,
19 | }
20 | }
21 |
22 | return {
23 | props: { data }, // will be passed to the page component as props
24 | }
25 | }
26 | ```
27 |
28 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
29 |
30 | ## Section 3
31 |
32 | Aenean eu lobortis nisi. Donec rutrum fringilla orci, vitae fermentum nisi porta eu. Nam sit amet odio lacus. Nulla eu fermentum metus. Suspendisse eu aliquet lectus, at fringilla massa. Pellentesque hendrerit iaculis metus id aliquet. Curabitur urna libero, molestie in lorem porta, imperdiet ultricies ex. Cras elementum aliquam lorem, non semper enim fermentum sed. Nullam egestas, elit at vulputate pretium, dui enim ornare erat, a tristique nulla risus id neque.
33 |
34 | ## Section 4
35 |
36 | Suspendisse vel massa elementum, porta odio pharetra, fringilla nunc. Proin vitae porttitor ex. Phasellus suscipit lorem augue, a pretium mauris porttitor ut. Interdum et malesuada fames ac ante ipsum primis in faucibus. Etiam interdum sollicitudin arcu et tristique. Ut consequat ullamcorper dolor, quis laoreet velit fermentum vitae. Aliquam erat volutpat.
37 |
--------------------------------------------------------------------------------
/components/Lesson/LessonHero.tsx:
--------------------------------------------------------------------------------
1 | export default function LessonHero(props) {
2 | return (
3 |
12 | )
13 | }
14 |
15 | // This function gets called at build time on server-side.
16 | // It won't be called on client-side, so you can even do
17 | // direct database queries. See the "Technical details" section.
18 | export async function getStaticProps() {
19 | // Call an external API endpoint to get posts.
20 | // You can use any data fetching library
21 | const res = await fetch("https://.../posts")
22 | const posts = await res.json()
23 |
24 | // By returning { props: { posts } }, the Blog component
25 | // will receive `posts` as a prop at build time
26 | return {
27 | props: {
28 | posts,
29 | },
30 | }
31 | }
32 |
33 | export default Blog
34 | ```
35 |
36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
37 |
38 | ## Section 1
39 |
40 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
41 |
42 | ## Section 2
43 |
44 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
45 |
46 | ## Section 3
47 |
48 | Aenean eu lobortis nisi. Donec rutrum fringilla orci, vitae fermentum nisi porta eu. Nam sit amet odio lacus. Nulla eu fermentum metus. Suspendisse eu aliquet lectus, at fringilla massa. Pellentesque hendrerit iaculis metus id aliquet. Curabitur urna libero, molestie in lorem porta, imperdiet ultricies ex. Cras elementum aliquam lorem, non semper enim fermentum sed. Nullam egestas, elit at vulputate pretium, dui enim ornare erat, a tristique nulla risus id neque.
49 |
--------------------------------------------------------------------------------
/content/courses/testing-your-first-application/app-install-and-overview.mdx:
--------------------------------------------------------------------------------
1 | # App Install & Overview
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
4 |
5 | ## Section 1
6 |
7 | ```js
8 | export async function getStaticProps(context) {
9 | const res = await fetch(`https://.../data`)
10 | const data = await res.json()
11 |
12 | if (!data) {
13 | return {
14 | notFound: true,
15 | }
16 | }
17 |
18 | return {
19 | props: { data }, // will be passed to the page component as props
20 | }
21 | }
22 | ```
23 |
24 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
25 |
26 | ## Section 2
27 |
28 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
29 |
30 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
31 |
32 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
33 |
34 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
35 |
--------------------------------------------------------------------------------
/components/Lesson/MultipleChoiceChallenge.tsx:
--------------------------------------------------------------------------------
1 | import { useActor } from "@xstate/react"
2 | import { useState } from "react"
3 |
4 | export default function LessonChallenge({
5 | progressService,
6 | lessonData,
7 | lessonPath,
8 | }) {
9 | const [answerIndicies, setAnswerChecked] = useState([])
10 | const [, progressSend] = useActor(progressService)
11 |
12 | const isIncorrectAnswer = (index) => {
13 | return (
14 | answerIndicies.includes(index) &&
15 | lessonData.challenges[0].correctAnswerIndex !== index
16 | )
17 | }
18 |
19 | return (
20 | <>
21 |
22 |
77 |
78 | >
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/components/Mobile/MobileProgress.tsx:
--------------------------------------------------------------------------------
1 | import { CheckIcon } from "@heroicons/react/solid"
2 | import { isLessonCompleted } from "../../utils/machineUtils"
3 |
4 | function classNames(...classes) {
5 | return classes.filter(Boolean).join(" ")
6 | }
7 |
8 | export default function LessonSteps({ course, content, progressService }) {
9 | return (
10 |
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/content/courses/cypress-fundamentals/how-to-write-a-test.mdx:
--------------------------------------------------------------------------------
1 | # How to Write a Test
2 |
3 | ```js
4 | // posts will be populated at build time by getStaticProps()
5 | function Blog({ posts }) {
6 | return (
7 |
8 | {posts.map((post) => (
9 |
{post.title}
10 | ))}
11 |
12 | )
13 | }
14 |
15 | // This function gets called at build time on server-side.
16 | // It won't be called on client-side, so you can even do
17 | // direct database queries. See the "Technical details" section.
18 | export async function getStaticProps() {
19 | // Call an external API endpoint to get posts.
20 | // You can use any data fetching library
21 | const res = await fetch("https://.../posts")
22 | const posts = await res.json()
23 |
24 | // By returning { props: { posts } }, the Blog component
25 | // will receive `posts` as a prop at build time
26 | return {
27 | props: {
28 | posts,
29 | },
30 | }
31 | }
32 |
33 | export default Blog
34 | ```
35 |
36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis lobortis maximus. Quisque egestas, sem fringilla egestas aliquet, turpis nisl placerat justo, vitae feugiat ligula urna in justo. Curabitur porta risus libero, tempus imperdiet arcu auctor sed. Praesent eget luctus tortor, vel hendrerit sapien. Phasellus quis ex ligula. Curabitur vehicula, lacus imperdiet ornare pulvinar, sem massa vulputate sem, eget mattis ex diam a lorem. Cras ac ex gravida magna vestibulum volutpat et et elit. Nullam vitae porttitor nulla, sed volutpat nisi.
37 |
38 | ## Section 1
39 |
40 | Sed elementum ut nulla nec sagittis. Sed orci neque, cursus id lacinia id, suscipit eget ligula. Etiam mollis dolor et libero dapibus facilisis. Phasellus erat ex, efficitur egestas lorem vitae, rhoncus convallis quam. Aliquam non laoreet ipsum. Nullam luctus, felis ut imperdiet mattis, ligula ligula ultricies orci, at dignissim erat odio id ligula. Praesent placerat sapien eu urna hendrerit pulvinar. Integer eget diam enim. Cras id massa porta, feugiat mi ac, euismod dui. Ut sed justo augue. Quisque viverra enim id nisi placerat, nec tincidunt urna facilisis. Nam vitae vestibulum ex, quis malesuada orci. Nulla a mi nec sem consectetur luctus non placerat urna.
41 |
42 | ## Section 2
43 |
44 | Vestibulum congue consectetur quam in mattis. Maecenas condimentum gravida arcu eu blandit. Aenean lorem risus, imperdiet eu bibendum vitae, imperdiet in diam. Phasellus quis risus ac leo condimentum egestas quis in tortor. Pellentesque at aliquet urna. Proin quis est tincidunt, molestie sem sit amet, aliquam leo. Suspendisse egestas lacinia dignissim.
45 |
46 | ## Section 3
47 |
48 | Aenean eu lobortis nisi. Donec rutrum fringilla orci, vitae fermentum nisi porta eu. Nam sit amet odio lacus. Nulla eu fermentum metus. Suspendisse eu aliquet lectus, at fringilla massa. Pellentesque hendrerit iaculis metus id aliquet. Curabitur urna libero, molestie in lorem porta, imperdiet ultricies ex. Cras elementum aliquam lorem, non semper enim fermentum sed. Nullam egestas, elit at vulputate pretium, dui enim ornare erat, a tristique nulla risus id neque.
49 |
50 | ## Section 4
51 |
52 | Suspendisse vel massa elementum, porta odio pharetra, fringilla nunc. Proin vitae porttitor ex. Phasellus suscipit lorem augue, a pretium mauris porttitor ut. Interdum et malesuada fames ac ante ipsum primis in faucibus. Etiam interdum sollicitudin arcu et tristique. Ut consequat ullamcorper dolor, quis laoreet velit fermentum vitae. Aliquam erat volutpat.
53 |
--------------------------------------------------------------------------------
/components/Course/CourseContent.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from "next/dynamic"
2 | import { CheckIcon } from "@heroicons/react/outline"
3 | import CourseProgress from "./CourseProgress"
4 |
5 | const CourseNextLessonBtn = dynamic(() => import("./CourseNextLessonBtn"), {
6 | ssr: false,
7 | })
8 |
9 | export default function CourseContent({
10 | title,
11 | lessons,
12 | learnFeatures,
13 | progressService,
14 | course,
15 | }) {
16 | const stats = [{ label: "Lessons", value: lessons.length }]
17 |
18 | return (
19 | <>
20 |
21 |