Adding tests.
This commit is contained in:
10
cypress.config.js
Normal file
10
cypress.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const { defineConfig } = require("cypress");
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'http://localhost:3000',
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
// implement node event listeners here
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
31
cypress/e2e/episodePage.cy.js
Normal file
31
cypress/e2e/episodePage.cy.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
describe('Podcast episode page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/podcast/mechanics-of-react-a-beginners-intro-to-react')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads correctly', () => {
|
||||||
|
// Check that the page title is correct
|
||||||
|
cy.title().should('equal', `Mechanics of React: A Beginner's Intro To React | The React Show`)
|
||||||
|
|
||||||
|
// Check that the date is displayed
|
||||||
|
cy.get('[data-cy=date]').should('contain', 'March 31, 2023')
|
||||||
|
|
||||||
|
// Check that the YouTube video is embedded and playable
|
||||||
|
cy.get('iframe[src^="https://www.youtube.com/embed/"]').should('be.visible')
|
||||||
|
|
||||||
|
// Check that the title is correct
|
||||||
|
cy.get('[data-cy=title]').should('contain', `[87] Mechanics of React: A Beginner's Intro To React`)
|
||||||
|
|
||||||
|
// Check that the description is correct
|
||||||
|
cy.get('[data-cy=description]').should('contain', 'Learn the fundamentals of React')
|
||||||
|
|
||||||
|
// Check that the transcript is correct
|
||||||
|
cy.get('[data-cy=transcript]').should('contain', '00:00:05 Brought to you from occupied Miwok territory by me')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should play the audio player when the "Listen" button is clicked', () => {
|
||||||
|
cy.get('[data-cy=audio-player-button]').first().should('have.attr', 'aria-label', 'Play')
|
||||||
|
cy.get('[data-cy=audio-player-button]').first().click()
|
||||||
|
cy.get('[data-cy=audio-player-button]').first().should('have.attr', 'aria-label', 'Pause')
|
||||||
|
})
|
||||||
|
})
|
||||||
26
cypress/e2e/episodes.cy.js
Normal file
26
cypress/e2e/episodes.cy.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
describe('Episodes component', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it.only('should render the component with episodes data', () => {
|
||||||
|
cy.get('[data-cy=episode-entry]').should('have.length', 15)
|
||||||
|
|
||||||
|
cy.get('[data-cy=episode-title]').first().should('contain', `[87] Mechanics of React: A Beginner's Intro To React`)
|
||||||
|
cy.get('[data-cy=episode-description]').first().should('contain', 'Learn the fundamentals of React by')
|
||||||
|
cy.get('[data-cy=episode-date]').first().should('contain', 'March 31, 2023')
|
||||||
|
|
||||||
|
cy.get('[data-cy=episode-title]').last().should('contain', '[73] A Fundamentally New React: My Journey with React Server Components')
|
||||||
|
cy.get('[data-cy=episode-description]').last().should('contain', 'React Sever Components are')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should navigate to the show notes page when the "Show notes" link is clicked', () => {
|
||||||
|
cy.get('[data-cy=show-notes-link]').first().click()
|
||||||
|
cy.url().should('include', '/podcast/mechanics-of-react-a-beginners-intro-to-react')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should navigate to the transcript section when the "Transcript" link is clicked', () => {
|
||||||
|
cy.get('[data-cy=transcript-link]').last().click()
|
||||||
|
cy.url().should('include', '/podcast/a-fundamentally-new-react-my-journey-with-react-server-components#transcript')
|
||||||
|
})
|
||||||
|
})
|
||||||
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
||||||
25
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
20
cypress/support/e2e.js
Normal file
20
cypress/support/e2e.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/e2e.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
2301
package-lock.json
generated
2301
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
|
"cypress": "^12.9.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,64 +3,6 @@ import Link from 'next/link'
|
|||||||
import { Container } from '@/components/Container'
|
import { Container } from '@/components/Container'
|
||||||
import Episodes from '@/components/Episodes'
|
import Episodes from '@/components/Episodes'
|
||||||
|
|
||||||
function Skeleton({ width, height, className, color }) {
|
|
||||||
const w = width ? '' : 'w-full';
|
|
||||||
const c = color ? color : 'bg-slate-200';
|
|
||||||
return (
|
|
||||||
<div className={`animate-pulse flex ${c} ${w} ${className}`} style={{ width, height }} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function EpisodeEntryLoading() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="py-10 sm:py-12"
|
|
||||||
>
|
|
||||||
<Container>
|
|
||||||
<div className="flex flex-col items-start w-full">
|
|
||||||
<div className="order-first font-mono text-sm leading-7 text-slate-500">
|
|
||||||
<Skeleton height="1.75rem" width="140px" />
|
|
||||||
</div>
|
|
||||||
<h2
|
|
||||||
className="mt-2 text-lg font-bold text-slate-900 w-full"
|
|
||||||
>
|
|
||||||
<Skeleton height="1.75rem" />
|
|
||||||
</h2>
|
|
||||||
<div className="mt-1 text-base leading-7 text-slate-700 w-full">
|
|
||||||
<Skeleton height="1rem" className="mt-3" />
|
|
||||||
<Skeleton height="1rem" className="mt-3" />
|
|
||||||
<Skeleton height="1rem" className="mt-3" />
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 flex items-center gap-4">
|
|
||||||
<Skeleton width="0.625rem" height="0.625rem" />
|
|
||||||
<span className="ml-3">
|
|
||||||
<Skeleton height="1.5rem" width="42.275px" />
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
className="text-sm font-bold text-slate-400"
|
|
||||||
>
|
|
||||||
/
|
|
||||||
</span>
|
|
||||||
<Skeleton height="1.5rem" width="80.175px" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Loading() {
|
|
||||||
// You can add any UI inside Loading, including a Skeleton.
|
|
||||||
return (
|
|
||||||
<div className="divide-y divide-slate-100 sm:mt-4 lg:mt-8 lg:border-t lg:border-slate-100">
|
|
||||||
{[0, 1, 2, 3, 4, 5, 6, 7].map((i) => (
|
|
||||||
<EpisodeEntryLoading key={i} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: "Weekly podcast focused on React, programming, and software engineering."
|
description: "Weekly podcast focused on React, programming, and software engineering."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function Transcript({ children }) {
|
|||||||
<>
|
<>
|
||||||
<hr className="my-12 border-gray-200" />
|
<hr className="my-12 border-gray-200" />
|
||||||
<h2 className="mt-2 text-3xl font-bold text-slate-900" id="transcript">Transcript</h2>
|
<h2 className="mt-2 text-3xl font-bold text-slate-900" id="transcript">Transcript</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4" data-cy="transcript">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -119,12 +119,13 @@ export default async function Page({ params }) {
|
|||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<PlayButtonClient audioPlayerData={audioPlayerData} size="large" />
|
<PlayButtonClient audioPlayerData={audioPlayerData} size="large" />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="mt-2 text-4xl font-bold text-slate-900">
|
<h1 className="mt-2 text-4xl font-bold text-slate-900" data-cy="title">
|
||||||
[{episode.num}] {episode.title}
|
[{episode.num}] {episode.title}
|
||||||
</h1>
|
</h1>
|
||||||
<FormattedDate
|
<FormattedDate
|
||||||
date={date}
|
date={date}
|
||||||
className="order-first font-mono text-sm leading-7 text-slate-500"
|
className="order-first font-mono text-sm leading-7 text-slate-500"
|
||||||
|
data-cy="date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,6 +143,7 @@ export default async function Page({ params }) {
|
|||||||
<div
|
<div
|
||||||
className="prose prose-slate mt-14 [&>h2]:mt-12 [&>h2]:flex [&>h2]:items-center [&>h2]:font-mono [&>h2]:text-sm [&>h2]:font-medium [&>h2]:leading-7 [&>h2]:text-slate-900 [&>h2]:before:mr-3 [&>h2]:before:h-3 [&>h2]:before:w-1.5 [&>h2]:before:rounded-r-full [&>h2]:before:bg-cyan-200 [&>ul]:mt-6 [&>ul]:list-['\2013\20'] [&>ul]:pl-5 [&>h2:nth-of-type(3n+2)]:before:bg-indigo-200 [&>h2:nth-of-type(3n)]:before:bg-violet-200"
|
className="prose prose-slate mt-14 [&>h2]:mt-12 [&>h2]:flex [&>h2]:items-center [&>h2]:font-mono [&>h2]:text-sm [&>h2]:font-medium [&>h2]:leading-7 [&>h2]:text-slate-900 [&>h2]:before:mr-3 [&>h2]:before:h-3 [&>h2]:before:w-1.5 [&>h2]:before:rounded-r-full [&>h2]:before:bg-cyan-200 [&>ul]:mt-6 [&>ul]:list-['\2013\20'] [&>ul]:pl-5 [&>h2:nth-of-type(3n+2)]:before:bg-indigo-200 [&>h2:nth-of-type(3n)]:before:bg-violet-200"
|
||||||
dangerouslySetInnerHTML={{ __html: episode.content || 'CONTENT' }}
|
dangerouslySetInnerHTML={{ __html: episode.content || 'CONTENT' }}
|
||||||
|
data-cy="description"
|
||||||
/>
|
/>
|
||||||
{episode?.transcript && <Suspense fallback={<TranscriptNoPlayer episode={episode} />}>
|
{episode?.transcript && <Suspense fallback={<TranscriptNoPlayer episode={episode} />}>
|
||||||
<TranscriptWithPlayer episode={episode} />
|
<TranscriptWithPlayer episode={episode} />
|
||||||
|
|||||||
@@ -14,20 +14,23 @@ function EpisodeEntry({ episode }) {
|
|||||||
<article
|
<article
|
||||||
aria-labelledby={`episode-${episode.id}-title`}
|
aria-labelledby={`episode-${episode.id}-title`}
|
||||||
className="py-10 sm:py-12"
|
className="py-10 sm:py-12"
|
||||||
|
data-cy="episode-entry"
|
||||||
>
|
>
|
||||||
<Container>
|
<Container>
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
<h2
|
<h2
|
||||||
id={`episode-${episode.id}-title`}
|
id={`episode-${episode.id}-title`}
|
||||||
className="mt-2 text-lg font-bold text-slate-900"
|
className="mt-2 text-lg font-bold text-slate-900"
|
||||||
|
data-cy="episode-title"
|
||||||
>
|
>
|
||||||
<Link href={`/podcast/${episode?.slug}`}>[{episode.num}] {episode.title}</Link>
|
<Link data-cy="show-notes-link" href={`/podcast/${episode?.slug}`}>[{episode.num}] {episode.title}</Link>
|
||||||
</h2>
|
</h2>
|
||||||
<FormattedDate
|
<FormattedDate
|
||||||
date={date}
|
date={date}
|
||||||
className="order-first font-mono text-sm leading-7 text-slate-500"
|
className="order-first font-mono text-sm leading-7 text-slate-500"
|
||||||
|
data-cy="episode-date"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-base leading-7 text-slate-700">
|
<p className="mt-1 text-base leading-7 text-slate-700" data-cy="episode-description">
|
||||||
{episode.description}
|
{episode.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 flex items-center gap-4">
|
<div className="mt-4 flex items-center gap-4">
|
||||||
@@ -62,6 +65,7 @@ function EpisodeEntry({ episode }) {
|
|||||||
href={`/podcast/${episode.slug}#transcript`}
|
href={`/podcast/${episode.slug}#transcript`}
|
||||||
className="flex items-center text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900"
|
className="flex items-center text-sm font-bold leading-6 text-pink-500 hover:text-pink-700 active:text-pink-900"
|
||||||
aria-label={`Transcript for episode ${episode.title}`}
|
aria-label={`Transcript for episode ${episode.title}`}
|
||||||
|
data-cy="transcript-link"
|
||||||
>
|
>
|
||||||
Transcript
|
Transcript
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export function PlayButton({ player, size = 'large' }) {
|
|||||||
)}
|
)}
|
||||||
onClick={player.toggle}
|
onClick={player.toggle}
|
||||||
aria-label={player.playing ? 'Pause' : 'Play'}
|
aria-label={player.playing ? 'Pause' : 'Play'}
|
||||||
|
data-cy="audio-player-button"
|
||||||
>
|
>
|
||||||
<div className="absolute -inset-3 md:hidden" />
|
<div className="absolute -inset-3 md:hidden" />
|
||||||
{player.playing ? (
|
{player.playing ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user