diff --git a/apps/cookbook/src/components/external-link.spec.tsx b/apps/cookbook/src/components/external-link.spec.tsx new file mode 100644 index 0000000..dcf7e75 --- /dev/null +++ b/apps/cookbook/src/components/external-link.spec.tsx @@ -0,0 +1,46 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { expect, test } from 'vitest'; +import { ExternalLink } from './external-link'; + +test(`${ExternalLink.name} renders correctly without UTM parameters`, () => { + const { container } = render( + + Let's cook some tests + , + ); + + const linkEl = container.querySelector('a'); + + expect + .soft(linkEl) + .toHaveAttribute( + 'href', + 'https://courses.marmicode.io/?utm_source=cookbook', + ); + expect.soft(linkEl).toHaveTextContent(`Let's cook some tests`); + expect.soft(linkEl).toHaveAttribute('target', '_blank'); + expect.soft(linkEl).toHaveAttribute('rel', 'noopener noreferrer'); +}); + +test(`${ExternalLink.name} renders correctly with UTM parameters`, () => { + const { container } = render( + + Let's cook some tests + , + ); + + const linkEl = container.querySelector('a'); + + expect + .soft(linkEl) + .toHaveAttribute( + 'href', + 'https://courses.marmicode.io/?utm_source=cookbook&utm_medium=in-article&utm_campaign=prep-station-v20&utm_content=flushing-flusheffects', + ); +}); diff --git a/apps/cookbook/src/components/external-link.tsx b/apps/cookbook/src/components/external-link.tsx new file mode 100644 index 0000000..1c644be --- /dev/null +++ b/apps/cookbook/src/components/external-link.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +interface ExternalLinkProps { + href: string; + children: React.ReactNode; + className?: string; + medium?: 'footer' | 'in-article' | 'toc'; + campaign?: string; + content?: string; +} + +export function ExternalLink({ + href, + children, + className, + medium, + campaign, + content, +}: ExternalLinkProps) { + const url = new URL(href); + url.searchParams.set('utm_source', 'cookbook'); + if (medium) { + url.searchParams.set('utm_medium', medium); + } + if (campaign) { + url.searchParams.set('utm_campaign', campaign); + } + if (content) { + url.searchParams.set('utm_content', content); + } + + return ( + + {children} + + ); +} diff --git a/apps/cookbook/src/components/special-offers.module.css b/apps/cookbook/src/components/special-offers.module.css new file mode 100644 index 0000000..795b63e --- /dev/null +++ b/apps/cookbook/src/components/special-offers.module.css @@ -0,0 +1,62 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + + width: 80%; + max-width: 240px; + margin: 0 auto; + padding: 1rem; + + border: 1px solid #e0e0e0; + border-radius: 8px; + background: #fdfcfb; +} + +.link { + font-size: 0.8rem; + color: #6f2c91; + text-decoration: underline; +} + +.primary { + font-size: 0.875rem; + font-weight: bold; +} + +.image { + width: 100%; + border-radius: 4px; + margin-bottom: 0.5rem; +} + +.priceInfo { + color: #666; + font-size: 0.82rem; + margin-bottom: 0.5rem; +} + +.lifetimeAccess { + color: #333; +} + +.description { + color: #333; + font-size: 0.8rem; + line-height: 1.2; + margin: 0; +} + +.separator { + margin: 1rem 0; +} + +.moreSeasoning { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.moreSeasoningTitle { + font-size: 0.9rem; +} diff --git a/apps/cookbook/src/components/special-offers.tsx b/apps/cookbook/src/components/special-offers.tsx new file mode 100644 index 0000000..99772fb --- /dev/null +++ b/apps/cookbook/src/components/special-offers.tsx @@ -0,0 +1,72 @@ +import React, { JSX } from 'react'; + +import styles from './special-offers.module.css'; +import { ExternalLink } from './external-link'; + +export function SpecialOffers(): JSX.Element { + const medium = 'toc'; + return ( +
+ + Pragmatic Angular Testing + +
+ + + ๐Ÿ‘จ๐Ÿปโ€๐Ÿณ + + Let's cook some tests โ†’ + + +

+ ๐Ÿ’ฐ 80โ‚ฌ ยท 170โ‚ฌ ยท{' '} + Lifetime access +

+ +

+ Learn how to write reliable tests that survive upgrades and + refactorings. +

+ +
+ +
+ + + ๐Ÿง‚ + + Need more seasoning? + + + See code review/Q&A plans โ†’ + + + + Or let's plan a call โ†’ + +
+
+
+ ); +} diff --git a/apps/cookbook/src/components/youtube.spec.tsx b/apps/cookbook/src/components/youtube.spec.tsx index b5035d2..3f70022 100644 --- a/apps/cookbook/src/components/youtube.spec.tsx +++ b/apps/cookbook/src/components/youtube.spec.tsx @@ -12,7 +12,7 @@ test(`${Youtube.name} should render iframe`, async () => { expect .soft(iframeEl) .toHaveAttribute('src', 'https://www.youtube.com/embed/aIAKlhrex8c'); - expect.soft(iframeEl.style.aspectRatio).toBe('16 / 9'); - expect.soft(iframeEl.style.width).toBe('100%'); - expect.soft(iframeEl.title).toBe('Nx Implicit Libraries Video'); + expect.soft(iframeEl?.style.aspectRatio).toBe('16 / 9'); + expect.soft(iframeEl?.style.width).toBe('100%'); + expect.soft(iframeEl?.title).toBe('Nx Implicit Libraries Video'); }); diff --git a/apps/cookbook/src/css-modules.d.ts b/apps/cookbook/src/css-modules.d.ts new file mode 100644 index 0000000..3d673e2 --- /dev/null +++ b/apps/cookbook/src/css-modules.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.css' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/apps/cookbook/src/theme/TOCItems/Tree.tsx b/apps/cookbook/src/theme/TOCItems/Tree.tsx new file mode 100644 index 0000000..fe7688d --- /dev/null +++ b/apps/cookbook/src/theme/TOCItems/Tree.tsx @@ -0,0 +1,16 @@ +import type { WrapperProps } from '@docusaurus/types'; +import { SpecialOffers } from '@site/src/components/special-offers'; +import Tree from '@theme-original/TOCItems/Tree'; +import type TreeType from '@theme/TOCItems/Tree'; +import React, { ReactElement } from 'react'; + +type Props = WrapperProps; + +export default function TreeWrapper(props: Props): ReactElement { + return ( + <> + + + + ); +} diff --git a/apps/cookbook/static/img/course.png b/apps/cookbook/static/img/course.png new file mode 100644 index 0000000..0f7d4be Binary files /dev/null and b/apps/cookbook/static/img/course.png differ diff --git a/apps/cookbook/tsconfig.app.json b/apps/cookbook/tsconfig.app.json new file mode 100644 index 0000000..3b7de78 --- /dev/null +++ b/apps/cookbook/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + // This file is not used in compilation. It is here just for a nice editor experience. + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "composite": true, + "noEmit": false + }, + "exclude": ["src/**/*.spec.ts", "src/**/*.spec.tsx"] +} diff --git a/apps/cookbook/tsconfig.json b/apps/cookbook/tsconfig.json index 314eab8..d5aeac1 100644 --- a/apps/cookbook/tsconfig.json +++ b/apps/cookbook/tsconfig.json @@ -3,5 +3,14 @@ "extends": "@docusaurus/tsconfig", "compilerOptions": { "baseUrl": "." - } + }, + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "include": ["src/**/*.ts", "src/**/*.tsx"] } diff --git a/apps/cookbook/tsconfig.spec.json b/apps/cookbook/tsconfig.spec.json new file mode 100644 index 0000000..08f887c --- /dev/null +++ b/apps/cookbook/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "noEmit": false, + "types": ["@testing-library/jest-dom"] + }, + "files": ["test-setup.tsx"] +} diff --git a/apps/cookbook/vite.config.mts b/apps/cookbook/vite.config.mts index 9811ba9..de70866 100644 --- a/apps/cookbook/vite.config.mts +++ b/apps/cookbook/vite.config.mts @@ -1,24 +1,8 @@ -/// import { defineConfig } from 'vite'; - import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; export default defineConfig({ root: __dirname, cacheDir: '../../node_modules/.vite/cookbook', - plugins: [nxViteTsPaths()], - - test: { - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - - reporters: ['default'], - coverage: { - reportsDirectory: '../../coverage/cookbook', - provider: 'v8', - }, - setupFiles: ['./test-setup.tsx'], - }, }); diff --git a/apps/cookbook/vitest.config.mts b/apps/cookbook/vitest.config.mts new file mode 100644 index 0000000..0159660 --- /dev/null +++ b/apps/cookbook/vitest.config.mts @@ -0,0 +1,15 @@ +import { mergeConfig } from 'vitest/config'; +import viteConfig from './vite.config.mts'; + +export default mergeConfig(viteConfig, { + test: { + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/cookbook', + provider: 'v8', + }, + setupFiles: ['./test-setup.tsx'], + }, +}); diff --git a/bun.lockb b/bun.lockb index 42360c5..abf9c6f 100755 Binary files a/bun.lockb and b/bun.lockb differ