├── .eslintrc ├── .gitignore ├── .node-version ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── components ├── Course │ ├── CourseContent.tsx │ ├── CourseHero.tsx │ ├── CourseNextLessonBtn.tsx │ └── CourseProgress.tsx ├── Footer.tsx ├── Header.tsx ├── Home │ ├── HomeCourses.tsx │ ├── HomeFeatures.tsx │ ├── HomeHero.tsx │ └── HomeProgress.tsx ├── Layout.tsx ├── Lesson │ ├── CompleteLessonBtn.tsx │ ├── LessonBreadcrumbs.tsx │ ├── LessonHero.tsx │ ├── LessonLayout.tsx │ ├── LessonProgress.tsx │ ├── LessonToc.tsx │ ├── MultipleChoiceChallenge.tsx │ ├── NextLessonBtn.tsx │ └── SkipChallenge.tsx ├── Mobile │ ├── MobileCourses.tsx │ ├── MobileNav.tsx │ └── MobileProgress.tsx ├── Progress │ ├── CompletedLesson.tsx │ ├── IncompleteLesson.tsx │ └── ProgressLine.tsx └── Subscribe.tsx ├── content └── courses │ ├── cypress-fundamentals │ ├── command-chaining.mdx │ ├── cypress-runs-in-the-browser.mdx │ └── how-to-write-a-test.mdx │ ├── testing-foundations │ ├── knowing-what-to-test.mdx │ ├── manual-vs-automated-testing.mdx │ └── testing-is-a-mindset.mdx │ └── testing-your-first-application │ ├── app-install-and-overview.mdx │ ├── installing-cypress-and-writing-our-first-test.mdx │ └── setting-up-data-before-each-test.mdx ├── data └── courses.json ├── docs ├── .vuepress │ └── config.js ├── directory-structure │ └── index.md ├── images │ └── progress.png ├── index.md ├── machines │ └── index.md ├── pages │ └── index.md ├── scripts │ └── index.md └── utils │ └── index.md ├── lib └── fetch-courses.ts ├── machines ├── progressMachine.ts └── progressService.ts ├── next-env.d.ts ├── next-sitemap.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── [course] │ ├── [slug].tsx │ └── index.tsx ├── _app.tsx ├── _document.js ├── api │ ├── courses.js │ └── subscribe.js └── index.tsx ├── postcss.config.js ├── public ├── favicon.ico ├── images │ ├── courses │ │ ├── cypress-rwa.png │ │ └── store.png │ └── logo │ │ ├── cypress-logo.png │ │ └── cypress-logo.webp ├── robots.txt └── sitemap.xml ├── renovate.json ├── styles └── global.css ├── tailwind.config.js ├── tsconfig.json ├── types └── common.d.ts └── utils ├── machineUtils.ts └── mdxUtils.ts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next", 4 | "prettier" 5 | ], 6 | "plugins": [ 7 | "prettier" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # Cypress 38 | cypress/videos 39 | cypress/screenshots -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18.17.1 -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.17.1 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Real World Testing with Cypress - Course App 2 | 3 | This application is for the [Testing your first application course](https://learn.cypress.io/testing-your-first-application) on [learn.cypress.io](https://learn.cypress.io/). 4 | 5 | The `start` branch is the main branch for this repo and is the starting point for the course. The [final](https://github.com/cypress-io/cypress-realworld-testing-course-app/tree/final) branch contains the completed tests. 6 | -------------------------------------------------------------------------------- /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 |
22 |
23 |
24 |
25 |

26 | {title} 27 |

28 |
29 | 34 |
35 |
36 | 37 | {/* Right Column */} 38 |
39 |
40 | {/* Content area */} 41 |
42 |

43 | What You Will Learn 44 |

45 | 46 | {/* Features */} 47 |
48 |
49 | {learnFeatures.map((feature, index) => ( 50 |
51 |
52 |
60 |
61 | ))} 62 |
63 |
64 |
65 | 66 | {/* Stats course */} 67 |
68 |
69 | {stats.map((stat) => ( 70 |
74 |
75 | {stat.label} 76 |
77 |
78 | {stat.value} 79 |
80 |
81 | ))} 82 |
83 |
84 | 89 |
90 |
91 |
92 |
93 |
94 | 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /components/Course/CourseHero.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | 3 | export default function CourseHero({ title, description, image }) { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |

11 | 12 | {title} 13 | 14 |

15 |

16 | {description} 17 |

18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /components/Course/CourseNextLessonBtn.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import { isLessonCompleted } from "../../utils/machineUtils" 3 | 4 | export default function CourseNextLessonBtn({ 5 | lessons, 6 | progressService, 7 | course, 8 | }) { 9 | const incompleLessons = lessons 10 | .map((lesson) => { 11 | if (!isLessonCompleted(progressService, `${course}/${lesson.slug}`)) { 12 | return lesson.slug 13 | } 14 | }) 15 | .filter((lesson) => lesson !== undefined) 16 | 17 | const buttonText = () => { 18 | if (!incompleLessons.length) { 19 | return "Course Completed" 20 | } 21 | 22 | if (incompleLessons.length === lessons.length) { 23 | return "Start Course" 24 | } else { 25 | return "Next Lesson" 26 | } 27 | } 28 | 29 | const buttonURL = () => { 30 | if (!incompleLessons.length) { 31 | return "/" 32 | } else { 33 | return `${course}/${incompleLessons[0]}` 34 | } 35 | } 36 | 37 | return ( 38 |
39 | 40 | 44 | {buttonText()} 45 | 46 | 47 |
48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | const navigation = { 2 | main: [ 3 | { name: "Next.js", href: "https://nextjs.org/" }, 4 | { 5 | name: "Cypress", 6 | href: "https://www.cypress.io/", 7 | }, 8 | { 9 | name: "GitHub", 10 | href: "https://github.com/cypress-io/cypress", 11 | }, 12 | ], 13 | } 14 | 15 | export default function Footer() { 16 | return ( 17 | <> 18 |
19 | 75 | 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from "react" 2 | import { Popover, Transition } from "@headlessui/react" 3 | import MobileNav from "./Mobile/MobileNav" 4 | import Link from "next/link" 5 | import coursesJson from "../data/courses.json" 6 | import { MenuIcon } from "@heroicons/react/outline" 7 | import { ChevronDownIcon, CodeIcon } from "@heroicons/react/solid" 8 | 9 | function classNames(...classes) { 10 | return classes.filter(Boolean).join(" ") 11 | } 12 | 13 | export default function Header({ content, courses, progressService }) { 14 | const coursesJsonCourses = Object.keys(coursesJson) 15 | 16 | return ( 17 | <> 18 | 19 |