|
1 | 1 | import React, { ReactNode } from 'react'; |
| 2 | +import { WindowLocation } from '@reach/router'; |
2 | 3 | import { render, screen } from '@testing-library/react'; |
3 | 4 | import '@testing-library/jest-dom/extend-expect'; |
| 5 | +import { Helmet } from 'react-helmet'; |
4 | 6 | import If from './mdx/If'; |
5 | 7 | import CodeSnippet from '@ably/ui/core/CodeSnippet'; |
6 | 8 | import UserContext from 'src/contexts/user-context'; |
| 9 | +import MDXWrapper from './MDXWrapper'; |
7 | 10 |
|
8 | | -// Mock the dependencies we need for testing |
9 | | -jest.mock('./MDXWrapper', () => { |
10 | | - return { |
11 | | - __esModule: true, |
12 | | - default: ({ children, pageContext }: { children: ReactNode; pageContext: any }) => ( |
13 | | - <div data-testid="mdx-wrapper"> |
14 | | - {pageContext?.frontmatter?.title && <h1>{pageContext.frontmatter.title}</h1>} |
15 | | - <div data-testid="mdx-content">{children}</div> |
16 | | - </div> |
17 | | - ), |
18 | | - }; |
19 | | -}); |
| 11 | +const mockUseLayoutContext = jest.fn(() => ({ |
| 12 | + activePage: { language: 'javascript', languages: ['javascript'], product: 'pubsub' }, |
| 13 | +})); |
20 | 14 |
|
21 | 15 | // Mock the layout context |
22 | 16 | jest.mock('src/contexts/layout-context', () => ({ |
23 | | - useLayoutContext: () => ({ |
24 | | - activePage: { language: 'javascript' }, |
25 | | - }), |
| 17 | + useLayoutContext: () => mockUseLayoutContext(), |
26 | 18 | LayoutProvider: ({ children }: { children: ReactNode }) => <div data-testid="layout-provider">{children}</div>, |
27 | 19 | })); |
28 | 20 |
|
| 21 | +jest.mock('@reach/router', () => ({ |
| 22 | + useLocation: () => ({ pathname: '/docs/test-page' }), |
| 23 | +})); |
| 24 | + |
| 25 | +jest.mock('src/hooks/use-site-metadata', () => ({ |
| 26 | + useSiteMetadata: () => ({ |
| 27 | + canonicalUrl: (path: string) => `https://example.com${path}`, |
| 28 | + }), |
| 29 | +})); |
| 30 | + |
29 | 31 | // We need to mock minimal implementation of other dependencies that CodeSnippet might use |
30 | 32 | jest.mock('@ably/ui/core/Icon', () => { |
31 | 33 | return { |
@@ -235,3 +237,80 @@ channel.subscribe('event', (message: Ably.Types.Message) => { |
235 | 237 | expect(typescriptElement).toBeInTheDocument(); |
236 | 238 | }); |
237 | 239 | }); |
| 240 | + |
| 241 | +describe('MDXWrapper structured data', () => { |
| 242 | + const defaultPageContext = { |
| 243 | + frontmatter: { |
| 244 | + title: 'Test Page', |
| 245 | + meta_description: 'Test description', |
| 246 | + }, |
| 247 | + languages: [], |
| 248 | + layout: { mdx: true, leftSidebar: true, rightSidebar: true, searchBar: true, template: 'docs' }, |
| 249 | + }; |
| 250 | + |
| 251 | + const defaultLocation = { |
| 252 | + pathname: '/docs/test-page', |
| 253 | + } as WindowLocation; |
| 254 | + |
| 255 | + beforeEach(() => { |
| 256 | + mockUseLayoutContext.mockReturnValue({ |
| 257 | + activePage: { |
| 258 | + product: 'pubsub', |
| 259 | + language: 'javascript', |
| 260 | + languages: [], |
| 261 | + }, |
| 262 | + }); |
| 263 | + }); |
| 264 | + |
| 265 | + afterEach(() => { |
| 266 | + jest.clearAllMocks(); |
| 267 | + }); |
| 268 | + |
| 269 | + it('does not generate structured data when only one language is present', () => { |
| 270 | + render( |
| 271 | + <UserContext.Provider value={{ sessionState: { signedIn: false }, apps: [] }}> |
| 272 | + <MDXWrapper pageContext={defaultPageContext} location={defaultLocation}> |
| 273 | + <div>Test content</div> |
| 274 | + </MDXWrapper> |
| 275 | + </UserContext.Provider>, |
| 276 | + ); |
| 277 | + |
| 278 | + const helmet = Helmet.peek(); |
| 279 | + const jsonLdScript = helmet.scriptTags?.find((tag: { type?: string }) => tag.type === 'application/ld+json'); |
| 280 | + |
| 281 | + expect(jsonLdScript).toBeUndefined(); |
| 282 | + }); |
| 283 | + |
| 284 | + it('generates TechArticle structured data with multiple languages', () => { |
| 285 | + mockUseLayoutContext.mockReturnValue({ |
| 286 | + activePage: { |
| 287 | + product: 'pubsub', |
| 288 | + language: 'javascript', |
| 289 | + languages: ['javascript', 'python'], |
| 290 | + }, |
| 291 | + }); |
| 292 | + |
| 293 | + render( |
| 294 | + <UserContext.Provider value={{ sessionState: { signedIn: false }, apps: [] }}> |
| 295 | + <MDXWrapper pageContext={defaultPageContext} location={defaultLocation}> |
| 296 | + <div>Test content</div> |
| 297 | + </MDXWrapper> |
| 298 | + </UserContext.Provider>, |
| 299 | + ); |
| 300 | + |
| 301 | + const helmet = Helmet.peek(); |
| 302 | + const jsonLdScript = helmet.scriptTags?.find((tag: { type?: string }) => tag.type === 'application/ld+json'); |
| 303 | + |
| 304 | + expect(jsonLdScript).toBeDefined(); |
| 305 | + expect(jsonLdScript?.type).toBe('application/ld+json'); |
| 306 | + |
| 307 | + const structuredData = JSON.parse(jsonLdScript?.innerHTML || '{}'); |
| 308 | + expect(structuredData['@type']).toBe('TechArticle'); |
| 309 | + expect(structuredData.hasPart).toHaveLength(2); |
| 310 | + expect(structuredData.hasPart[0]['@type']).toBe('SoftwareSourceCode'); |
| 311 | + expect(structuredData.hasPart[0].programmingLanguage).toBe('JavaScript'); |
| 312 | + expect(structuredData.hasPart[1].programmingLanguage).toBe('Python'); |
| 313 | + expect(structuredData.hasPart[0].url).toBe('https://example.com/docs/test-page?lang=javascript'); |
| 314 | + expect(structuredData.hasPart[1].url).toBe('https://example.com/docs/test-page?lang=python'); |
| 315 | + }); |
| 316 | +}); |
0 commit comments