diff --git a/.eslintrc b/.eslintrc index f33dafbb16664..e9154ea5fb2a2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -35,6 +35,7 @@ "plugins": ["@typescript-eslint"], "globals": { "globalThis": false }, "rules": { + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "@typescript-eslint/consistent-type-imports": "error" } }, diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 00e2a9315be9c..f79989648b19d 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -50,7 +50,7 @@ jobs: enableCrossOsArchive: true - name: Build Next.js - run: npx turbo build + run: npx turbo deploy env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ secrets.TURBO_TEAM }} @@ -66,9 +66,6 @@ jobs: key: build-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('node_modules/.cache') }} enableCrossOsArchive: true - - name: Export Next.js static files - run: npx turbo export - - name: Upload Artifact uses: actions/upload-pages-artifact@v1 with: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7e573a6802105..b69bb2ff39a92 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -83,7 +83,13 @@ jobs: enableCrossOsArchive: true - name: Run Unit Tests - run: npx turbo test:ci + run: npx turbo test:unit -- --coverage + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + + - name: Run Storybook Tests + run: npx turbo test:storybook env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ secrets.TURBO_TEAM }} diff --git a/.gitignore b/.gitignore index 4a9bde3abf58c..855c41a9a9e92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,19 @@ # Commonly ignored Node.js files -node_modules/ +node_modules npm-debug.log -.npm/ +.npm # OSX system files, the bane of our existence .DS_Store .AppleDouble .LSOverride -# Next.js files +# Next.js Build Output .next build + +# Next.js Generated Files +public/robots.txt public/sitemap.xml public/en/feed/*.xml pages/en/blog/year-[0-9][0-9][0-9][0-9].md @@ -26,8 +29,6 @@ coverage # Storybook storybook-static -# Vercel Config +# Vercel Files .vercel - -# TurboRepo .turbo diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 283ef3270bc53..bb3cd3682e271 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -23,6 +23,7 @@ const preview: Preview = { basePath: '', }, }, + backgrounds: { disable: true }, }, }; @@ -39,7 +40,9 @@ export const decorators = [ } `} - +
+ +
diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 0000000000000..4be5d420b4108 --- /dev/null +++ b/.storybook/test-runner.ts @@ -0,0 +1,14 @@ +// TODO: Convert this to ESM once the bug is resolved +// https://github.com/storybookjs/test-runner/issues/293 + +const config = { + async postRender(page, _context) { + const rootElementId = '[data-test-id="story-root"]'; + const rootElement = await page.locator(rootElementId); + const content = await rootElement.innerHTML(); + expect(content).toBeDefined(); + expect(content).toMatchSnapshot(); + }, +}; + +module.exports = config; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23c9cf520f327..2dcc6f76866cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,8 +23,8 @@ Thank you for your interest in contributing to the Node.js Website. Before you p ```bash git clone git@github.com:/nodejs.org.git # SSH -gh repo clone /nodejs.org # GitHub CLI git clone https://github.com//nodejs.org.git # HTTPS +gh repo clone /nodejs.org # GitHub CLI ``` 3. Change into the nodejs.org directory. @@ -36,7 +36,9 @@ cd nodejs.org 4. Create a remote for keeping your fork as well as your local clone up-to-date. ```bash -git remote add upstream git@github.com:nodejs/nodejs.org.git +git remote add upstream git@github.com:nodejs/nodejs.org.git # SSH +git remote add upstream https://github.com/nodejs/nodejs.org.git # HTTPS +gh repo sync nodejs/nodejs.org # GitHub CLI ``` 5. Create a new branch for your work. @@ -85,8 +87,9 @@ npx turbo format - `npx turbo serve` runs Next.js's Local Development Server, listening by default on `http://localhost:3000/`. - `npx turbo build` builds the Application on Production mode. The output is by default within `.next` folder. -- `npx turbo export` exports the website from the `.next` into a fully static website. The output is by default within `build` folder. - - This is what it's used to deploy the website on our current Node.js servers. + - This is used for the Node.js Vercel Deployments (Preview & Production) +- `npx turbo deploy` builds the Application on Export Production Mode. The output is by default within `build` folder. + - This is used for the Node.js Legacy Website Server (DigitalOcean) - `npx turbo start` starts a web server running serving the built content from `npx turbo build` #### Other CLI options @@ -102,7 +105,10 @@ We also offer other commands that offer you assistance during your local develop - **Usage:** `npx turbo scripts:release-post -- --version=vXX.X.X --force` - `npx turbo storybook` starts Storybook's local server - `npx turbo storybook:build` builds Storybook as a static web application for publishing -- `npx turbo test` runs jest (unit-tests) locally +- `npx turbo test` runs all tests locally + - `npx turbo test:unit` runs jest (unit-tests) locally + - `npx turbo test:storybook` runs storybook test-runner tests + - `npx turbo test:storybook:snapshot` generates and updates snapshots for all storybook components. ## Creating Components @@ -178,7 +184,7 @@ Commits should be signed. You can read more about [Commit Signing][] here. Each new feature or bug fix should be accompanied by a unit test (when deemed valuable). We use [Jest][] as our test runner and [React Testing Library][] for our React unit tests. -We also use [Storybook][] to document our components. Each component should have a storybook story that documents the component's usage. +We also use [Storybook][] to document our components. Each component should have a storybook story that documents the component's usage. Snapshot testing of our components is directly done by taking snapshot of all Storybook stories, using [Storybook Test Runner][] and [Playwright][]. ### General Guidelines for Unit Tests @@ -299,6 +305,8 @@ If something is missing here, or you feel something is not well described, feel [Jest]: https://jestjs.io/ [React Testing Library]: https://testing-library.com/docs/react-testing-library/intro/ [Storybook]: https://storybook.js.org/ +[Storybook Test Runner]: https://storybook.js.org/addons/@storybook/test-runner#dom-snapshot-recipe +[Playwright]: https://playwright.dev/ [`react-intl`]: https://formatjs.io/docs/react-intl/ [Next.js]: https://nextjs.org/ [MDX]: https://mdxjs.com/ diff --git a/components/Api/DataTag/__snapshots__/index.stories.ts.snap b/components/Api/DataTag/__snapshots__/index.stories.ts.snap new file mode 100644 index 0000000000000..4cd2f15367a50 --- /dev/null +++ b/components/Api/DataTag/__snapshots__/index.stories.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Api/DataTag Blue smoke-test 1`] = ` + + M + +`; + +exports[`Api/DataTag Red smoke-test 1`] = ` + + E + +`; + +exports[`Api/DataTag Yellow smoke-test 1`] = ` + + C + +`; diff --git a/components/Api/DataTag/__tests__/__snapshots__/index.test.tsx.snap b/components/Api/DataTag/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 2bd80b1a138f5..0000000000000 --- a/components/Api/DataTag/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Data Tag component renders with blue background color when tag is 'M' 1`] = ` -
- - M - -
-`; - -exports[`Data Tag component renders with red background color when tag is 'E' 1`] = ` -
- - E - -
-`; - -exports[`Data Tag component renders with yellow background color when tag is 'C' 1`] = ` -
- - C - -
-`; diff --git a/components/Api/DataTag/__tests__/index.test.tsx b/components/Api/DataTag/__tests__/index.test.tsx deleted file mode 100644 index 8e4d5c7cda5f3..0000000000000 --- a/components/Api/DataTag/__tests__/index.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { render } from '@testing-library/react'; -import DataTag from '../index'; - -describe('Data Tag component', () => { - it(`renders with red background color when tag is 'E'`, () => { - const { container } = render(); - - expect(container).toHaveStyle('background-color: var(--danger6)'); - expect(container).toMatchSnapshot(); - }); - - it(`renders with yellow background color when tag is 'C'`, () => { - const { container } = render(); - - expect(container).toHaveStyle('background-color: var(--warning4)'); - expect(container).toMatchSnapshot(); - }); - - it(`renders with blue background color when tag is 'M'`, () => { - const { container } = render(); - - expect(container).toHaveStyle('background-color: var(--info6)'); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Api/JsonLink/__snapshots__/index.stories.ts.snap b/components/Api/JsonLink/__snapshots__/index.stories.ts.snap new file mode 100644 index 0000000000000..0d229a4b63323 --- /dev/null +++ b/components/Api/JsonLink/__snapshots__/index.stories.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Api/JsonLink Default smoke-test 1`] = ` + +`; diff --git a/components/Api/JsonLink/index.module.scss b/components/Api/JsonLink/index.module.scss new file mode 100644 index 0000000000000..b1e23af5b893b --- /dev/null +++ b/components/Api/JsonLink/index.module.scss @@ -0,0 +1,29 @@ +.json { + display: flex; + flex-wrap: wrap; + + a { + color: var(--color-text-secondary); + font-family: var(--sans-serif); + font-size: 1.4rem; + font-weight: var(--font-weight-regular); + margin-left: 0; + text-decoration: none !important; + text-transform: uppercase; + vertical-align: middle; + + span { + font-weight: var(--font-weight-regular); + vertical-align: middle; + } + + &:hover { + color: var(--brand-light); + } + + .FaRobotIcon { + margin-left: 0.5rem; + vertical-align: middle; + } + } +} diff --git a/components/Api/JsonLink/index.stories.ts b/components/Api/JsonLink/index.stories.ts new file mode 100644 index 0000000000000..a2bbbdcc01186 --- /dev/null +++ b/components/Api/JsonLink/index.stories.ts @@ -0,0 +1,14 @@ +import JsonLink from './index'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = { + args: { + version: 'v18', + fileName: 'documentation', + }, +}; + +export default { component: JsonLink } as Meta; diff --git a/components/Api/JsonLink/index.tsx b/components/Api/JsonLink/index.tsx new file mode 100644 index 0000000000000..8110de45c4de7 --- /dev/null +++ b/components/Api/JsonLink/index.tsx @@ -0,0 +1,22 @@ +import { FormattedMessage } from 'react-intl'; +import { FaRobot } from 'react-icons/fa'; +import styles from './index.module.scss'; +import type { FC } from 'react'; + +type JsonLinkProps = { + fileName: string; + version: string; +}; + +const JsonLink: FC = ({ fileName, version }) => ( + +); + +export default JsonLink; diff --git a/components/Api/SourceLink/__snapshots__/index.stories.ts.snap b/components/Api/SourceLink/__snapshots__/index.stories.ts.snap new file mode 100644 index 0000000000000..6ee8ce731c797 --- /dev/null +++ b/components/Api/SourceLink/__snapshots__/index.stories.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Api/SourceLink Default smoke-test 1`] = ` + +`; diff --git a/components/Api/SourceLink/index.module.scss b/components/Api/SourceLink/index.module.scss new file mode 100644 index 0000000000000..afac99ee67501 --- /dev/null +++ b/components/Api/SourceLink/index.module.scss @@ -0,0 +1,5 @@ +.sourceLinkComponent { + font-size: var(--font-size-body3); + font-weight: var(--font-weight-semibold); + text-transform: uppercase; +} diff --git a/components/Api/SourceLink/index.stories.ts b/components/Api/SourceLink/index.stories.ts new file mode 100644 index 0000000000000..41848191afc79 --- /dev/null +++ b/components/Api/SourceLink/index.stories.ts @@ -0,0 +1,14 @@ +import SourceLink from './index'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = { + args: { + version: '1.0.0', + link: 'http://nodejs.org/version/1.0.0', + }, +}; + +export default { component: SourceLink } as Meta; diff --git a/components/Api/SourceLink/index.tsx b/components/Api/SourceLink/index.tsx new file mode 100644 index 0000000000000..da253e670ebc1 --- /dev/null +++ b/components/Api/SourceLink/index.tsx @@ -0,0 +1,20 @@ +import { FormattedMessage } from 'react-intl'; + +import styles from './index.module.scss'; +import type { FC } from 'react'; + +type SourceLinkProps = { + link: string; + version: string; +}; + +const SourceLink: FC = ({ version, link }) => ( +

+ {' '} + + {link} + +

+); + +export default SourceLink; diff --git a/components/Api/Stability/__snapshots__/index.stories.tsx.snap b/components/Api/Stability/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..eb19284dda4e5 --- /dev/null +++ b/components/Api/Stability/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Api/Stability Default smoke-test 1`] = ` +
+ Stability: 0 - Insert the text of your Alert here +
+`; diff --git a/components/Api/Stability/index.module.scss b/components/Api/Stability/index.module.scss new file mode 100644 index 0000000000000..e375fc8dc780f --- /dev/null +++ b/components/Api/Stability/index.module.scss @@ -0,0 +1,36 @@ +.stability { + border-radius: 4px; + color: #fff; + line-height: 1.5; + margin: 0 0 1rem; + padding: 1rem; + + p { + display: inline; + margin: 0; + } + + a { + color: #fff; + + code { + color: #fff !important; + } + } + + &Level0 { + background-color: var(--danger5); + } + + &Level1 { + background-color: var(--warning4); + } + + &Level2 { + background-color: var(--brand5); + } + + &Level3 { + background-color: var(--info5); + } +} diff --git a/components/Api/Stability/index.stories.tsx b/components/Api/Stability/index.stories.tsx new file mode 100644 index 0000000000000..f59b469db246a --- /dev/null +++ b/components/Api/Stability/index.stories.tsx @@ -0,0 +1,24 @@ +import Stability from './index'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = { + args: { + stability: 0, + children: 'Insert the text of your Alert here', + }, + argTypes: { + stability: { + control: { + type: 'range', + min: 0, + max: 3, + step: 1, + }, + }, + }, +}; + +export default { component: Stability } as Meta; diff --git a/components/Api/Stability/index.tsx b/components/Api/Stability/index.tsx new file mode 100644 index 0000000000000..44f6bb00c3cdc --- /dev/null +++ b/components/Api/Stability/index.tsx @@ -0,0 +1,23 @@ +import { FormattedMessage } from 'react-intl'; +import styles from './index.module.scss'; +import type { PropsWithChildren, FC } from 'react'; + +type StabilityProps = PropsWithChildren<{ stability: number }>; + +const getStabilityClass = (stability: number) => { + const style = styles[`stabilityLevel${stability}`]; + if (!style) throw new Error(`Unknown stability level: ${stability}`); + return style; +}; + +const Stability: FC = ({ stability, children }) => ( +
+ + {children} +
+); + +export default Stability; diff --git a/components/Article/Alert/__snapshots__/index.stories.ts.snap b/components/Article/Alert/__snapshots__/index.stories.ts.snap new file mode 100644 index 0000000000000..6b426fa60a183 --- /dev/null +++ b/components/Article/Alert/__snapshots__/index.stories.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/Alert Default smoke-test 1`] = ` +
+ This is an alert +
+`; diff --git a/components/Article/Alert/__tests__/__snapshots__/index.test.tsx.snap b/components/Article/Alert/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index abc4e76fd5f6a..0000000000000 --- a/components/Article/Alert/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Alert component should render correctly 1`] = ` -
-
-
-`; - -exports[`Alert component should support passing children into the component 1`] = ` -
-
- This is an alert -
-
-`; diff --git a/components/Article/Alert/__tests__/index.test.tsx b/components/Article/Alert/__tests__/index.test.tsx deleted file mode 100644 index b5e7b898f47ad..0000000000000 --- a/components/Article/Alert/__tests__/index.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { render } from '@testing-library/react'; -import Alert from '../index'; - -describe('Alert component', () => { - it('should render correctly', () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); - }); - - it('should support passing children into the component', () => { - const { container } = render(This is an alert); - - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Article/AuthorList/Author/__snapshots__/index.stories.tsx.snap b/components/Article/AuthorList/Author/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..158dc00988bb5 --- /dev/null +++ b/components/Article/AuthorList/Author/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/AuthorList/Author Default smoke-test 1`] = ` +
  • + + nodejs + +
  • +`; + +exports[`Article/AuthorList/Author WithourUsername smoke-test 1`] = ` +
  • + + + +
  • +`; diff --git a/components/Article/AuthorList/Author/__tests__/__snapshots__/index.test.tsx.snap b/components/Article/AuthorList/Author/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index f7d9312442eb7..0000000000000 --- a/components/Article/AuthorList/Author/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Author component does not render without a username 1`] = ` -
    -
  • - - - -
  • -
    -`; - -exports[`Author component renders correctly 1`] = ` -
    -
  • - - test-author - -
  • -
    -`; diff --git a/components/Article/AuthorList/Author/__tests__/index.test.tsx b/components/Article/AuthorList/Author/__tests__/index.test.tsx deleted file mode 100644 index b1db41e5d4742..0000000000000 --- a/components/Article/AuthorList/Author/__tests__/index.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { render } from '@testing-library/react'; -import { IntlProvider } from 'react-intl'; -import Author from '..'; - -describe('Author component', () => { - it('renders correctly', () => { - const username = 'test-author'; - const { container } = render( - {}}> - - - ); - expect(container).toMatchSnapshot(); - }); - - it('does not render without a username', () => { - const { container } = render( - {}}> - - - ); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Article/AuthorList/Author/index.stories.tsx b/components/Article/AuthorList/Author/index.stories.tsx new file mode 100644 index 0000000000000..1384b2d08e898 --- /dev/null +++ b/components/Article/AuthorList/Author/index.stories.tsx @@ -0,0 +1,21 @@ +import Author from './index'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = { + args: { + username: 'nodejs', + size: 60, + }, +}; + +export const WithourUsername: Story = { + args: { + username: '', + size: 0, + }, +}; + +export default { component: Author } as Meta; diff --git a/components/Article/AuthorList/__snapshots__/index.stories.tsx.snap b/components/Article/AuthorList/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..d5221b35d3d26 --- /dev/null +++ b/components/Article/AuthorList/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/AuthorList Default smoke-test 1`] = ` +
    + Article Authors + +
    +`; diff --git a/components/Article/AuthorList/__tests__/__snapshots__/authors-list.test.tsx.snap b/components/Article/AuthorList/__tests__/__snapshots__/authors-list.test.tsx.snap deleted file mode 100644 index a5b6115c59044..0000000000000 --- a/components/Article/AuthorList/__tests__/__snapshots__/authors-list.test.tsx.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AuthorsList component renders correctly 1`] = ` -
    -
    - components.article.authorList.title - -
    -
    -`; diff --git a/components/Article/AuthorList/__tests__/authors-list.test.tsx b/components/Article/AuthorList/__tests__/authors-list.test.tsx deleted file mode 100644 index c1cb4aaff940b..0000000000000 --- a/components/Article/AuthorList/__tests__/authors-list.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { render } from '@testing-library/react'; -import { IntlProvider } from 'react-intl'; -import AuthorsList from '..'; - -describe('AuthorsList component', () => { - it('renders correctly', () => { - const authors = ['test-author', 'another-test-author']; - const { container } = render( - {}}> - - - ); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Article/BlockQuote/__snapshots__/index.stories.tsx.snap b/components/Article/BlockQuote/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..eef1a32fb5667 --- /dev/null +++ b/components/Article/BlockQuote/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/BlockQuote Default smoke-test 1`] = ` +
    + This is a block quote +
    +`; + +exports[`Article/BlockQuote Empty smoke-test 1`] = ` +
    +
    +`; + +exports[`Article/BlockQuote MultipleParagraph smoke-test 1`] = ` +
    +

    + This is a block quote 1 +

    +

    + This is a block quote 2 +

    +
    +`; diff --git a/components/Article/BlockQuote/__tests__/__snapshots__/index.test.tsx.snap b/components/Article/BlockQuote/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index d26d87cee0ea7..0000000000000 --- a/components/Article/BlockQuote/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BlockQuote component should render correctly 1`] = ` -
    -
    -
    -`; - -exports[`BlockQuote component should support passing children into the component 1`] = ` -
    -
    - This is a block quote -
    -
    -`; - -exports[`BlockQuote component should support passing multiple children into the component 1`] = ` -
    -
    -

    - This is a block quote -

    -

    - This is a block quote -

    -
    -
    -`; diff --git a/components/Article/BlockQuote/__tests__/index.test.tsx b/components/Article/BlockQuote/__tests__/index.test.tsx deleted file mode 100644 index 8f5909ef68c66..0000000000000 --- a/components/Article/BlockQuote/__tests__/index.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { render } from '@testing-library/react'; -import BlockQuote from '../index'; - -describe('BlockQuote component', () => { - it('should render correctly', () => { - const { container } = render(
    ); - - expect(container).toMatchSnapshot(); - }); - - it('should support passing children into the component', () => { - const { container } = render( -
    This is a block quote
    - ); - - expect(container).toMatchSnapshot(); - }); - - it('should support passing multiple children into the component', () => { - const { container } = render( -
    -

    This is a block quote

    -

    This is a block quote

    -
    - ); - - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Article/BlockQuote/index.stories.tsx b/components/Article/BlockQuote/index.stories.tsx index 128405600d05c..4d264bec953b2 100644 --- a/components/Article/BlockQuote/index.stories.tsx +++ b/components/Article/BlockQuote/index.stories.tsx @@ -10,6 +10,8 @@ export const Default: Story = { }, }; +export const Empty: Story = {}; + export const MultipleParagraph: Story = { args: { children: [ diff --git a/components/Article/Codebox/__snapshots__/index.stories.tsx.snap b/components/Article/Codebox/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..decf2593ea9e9 --- /dev/null +++ b/components/Article/Codebox/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/Codebox Default smoke-test 1`] = ` +
    +  
    +
    + +
    + +
    +
    + + const + + a + + = + + + 1 + + + ; + +
    +
    +`; + +exports[`Article/Codebox MultiLang smoke-test 1`] = ` +
    +  
    +
    + + +
    + +
    +
    + + const + + http + + = + + + require + + + ( + + + 'http' + + + ) + + + ; + +
    +
    +`; diff --git a/components/Article/Codebox/__tests__/__snapshots__/index.test.tsx.snap b/components/Article/Codebox/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..7783af5875145 --- /dev/null +++ b/components/Article/Codebox/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,140 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Codebox component (multiple langs) switch between languages 1`] = ` +
    +
    +    
    +
    + + +
    + +
    +
    + + const + + http + + = + + + + require + + + ( + + + 'http' + + + ) + + + ; + + + +
    +
    +
    +`; + +exports[`Codebox component (multiple langs) switch between languages 2`] = ` +
    +
    +    
    +
    + + +
    + +
    +
    + + import + + http + + from + + + + 'http' + + + ; + +
    +
    +
    +`; diff --git a/components/Article/Codebox/__tests__/index.test.tsx b/components/Article/Codebox/__tests__/index.test.tsx new file mode 100644 index 0000000000000..5ceda6c99f93e --- /dev/null +++ b/components/Article/Codebox/__tests__/index.test.tsx @@ -0,0 +1,77 @@ +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from 'react-intl'; + +import Codebox, { replaceLabelLanguages, replaceLanguages } from '../index'; + +jest.mock('isomorphic-dompurify', () => { + return { + sanitize: jest.fn().mockImplementation(source => source), + }; +}); + +describe('Replacer tests', (): void => { + it('replaceLabelLanguages', (): void => { + expect(replaceLabelLanguages('language-console')).toBe('language-bash'); + }); + + it('replaceLanguages', (): void => { + expect(replaceLanguages('language-mjs')).toBe('language-js'); + expect(replaceLanguages('language-cjs')).toBe('language-js'); + expect(replaceLanguages('language-javascript')).toBe('language-js'); + expect(replaceLanguages('language-console')).toBe('language-bash'); + expect(replaceLanguages('language-shell')).toBe('language-bash'); + }); +}); + +describe('Codebox component (one lang)', (): void => { + const code = 'const a = 1;'; + + it('should copy content', async () => { + const user = userEvent.setup(); + + render( + {}}> + +
    {code}
    +
    +
    + ); + + const navigatorClipboardWriteTextSpy = jest.spyOn( + navigator.clipboard, + 'writeText' + ); + + const buttonElement = screen.getByText('components.codeBox.copy'); + await user.click(buttonElement); + + expect(navigatorClipboardWriteTextSpy).toHaveBeenCalledTimes(1); + expect(navigatorClipboardWriteTextSpy).toHaveBeenCalledWith(code); + }); +}); + +describe('Codebox component (multiple langs)', (): void => { + const code = `const http = require('http'); +-------------- +import http from 'http';`; + + it('switch between languages', async () => { + const user = userEvent.setup(); + + const { container } = render( + {}}> + +
    {code}
    +
    +
    + ); + + expect(container).toMatchSnapshot(); + + const buttonElement = await screen.findByText('mjs'); + await user.click(buttonElement); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/components/Article/Codebox/dark.module.scss b/components/Article/Codebox/dark.module.scss new file mode 100644 index 0000000000000..397cd95264bf7 --- /dev/null +++ b/components/Article/Codebox/dark.module.scss @@ -0,0 +1,90 @@ +@mixin darkStyles { + background: var(--black9); + color: var(--black2); + + :global .token { + &.comment, + &.prolog, + &.doctype, + &.cdata { + color: #8292a2; + } + + &.operator, + &.punctuation { + color: var(--black2); + } + + &.namespace { + opacity: 0.7; + } + + &.property, + &.tag, + &.constant, + &.symbol, + &.deleted { + color: #f92672; + } + + &.boolean { + color: #ae81ff; + } + + &.selector, + &.attr-name, + &.char, + &.builtin, + &.inserted { + color: #a6e22e; + } + + &.entity, + &.url, + .language-css &.string, + .style &.string, + &.variable { + color: #f8f8f2; + } + + &.atrule, + &.attr-value, + &.class-name { + color: #e6db74; + } + + &.function { + color: var(--warning3); + } + + &.string { + color: var(--brand3); + } + + &.keyword { + color: var(--info3); + } + + &.number { + color: var(--purple3); + } + + &.regex, + &.important { + color: #fd971f; + } + + &.important, + &.bold { + font-weight: var(--font-weight-bold); + } + + &.italic { + font-style: italic; + } + + &.entity { + cursor: help; + } + } +} diff --git a/components/Article/Codebox/index.module.scss b/components/Article/Codebox/index.module.scss new file mode 100644 index 0000000000000..cdf343574c34e --- /dev/null +++ b/components/Article/Codebox/index.module.scss @@ -0,0 +1,99 @@ +@import './light.module'; +@import './dark.module'; +@import '../../../styles/code'; + +.pre { + @extend %codeBaseStyles; + + .top { + display: flex; + flex-direction: row; + justify-content: space-between; + + .lang, + .copy { + align-items: center; + cursor: pointer; + display: inherit; + font-size: var(--font-size-code); + height: 23px; + justify-content: center; + width: 86px; + } + + .langBox { + background-color: var(--black4); + border-radius: 0 0 0.3rem 0; + display: flex; + flex-direction: row; + justify-content: center; + + .lang { + background-color: var(--black4); + border-width: 0; + color: var(--black9); + padding: 0 16px; + width: max-content; + + &:last-of-type { + border-radius: 0 0 0.3rem 0; + } + + &:hover { + background-color: var(--black5); + } + } + + .lang.selected { + font-weight: 600; + } + } + + .copy { + background-color: var(--black9); + border-radius: 0 0 0 0.3rem; + border-width: 0; + color: white; + + &:hover { + background-color: var(--black8); + } + + i { + padding: 0; + } + } + } + + .content { + margin: 1em; + } +} + +[data-theme='light'] { + .pre { + @include lightStyles; + } +} + +[data-theme='dark'] { + .pre { + @include darkStyles; + + .top { + span { + background-color: var(--black3); + color: var(--black9); + } + + .copy { + background-color: var(--brand8); + color: white; + + &:hover { + background-color: var(--brand6); + } + } + } + } +} diff --git a/components/Article/Codebox/index.stories.tsx b/components/Article/Codebox/index.stories.tsx new file mode 100644 index 0000000000000..94b48832d64bf --- /dev/null +++ b/components/Article/Codebox/index.stories.tsx @@ -0,0 +1,37 @@ +import Codebox from './index'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; +import type { FC } from 'react'; + +type DecoratedCodeBoxProps = { language: string[]; code: string[] }; + +const DecoratedCodeBox: FC = ({ language, code }) => ( + +
    {code.join('--------------\n')}
    +
    +); + +type Story = StoryObj; +type Meta = MetaObj; + +const singleLangCode = ['const a = 1;']; + +export const Default: Story = { + args: { + language: ['language-js'], + code: singleLangCode, + }, +}; + +const multiLangCode = [ + "const http = require('http');", + "import http from 'http';", +]; + +export const MultiLang: Story = { + args: { + language: ['language-cjs', 'language-mjs'], + code: multiLangCode, + }, +}; + +export default { component: DecoratedCodeBox } as Meta; diff --git a/components/Article/Codebox/index.tsx b/components/Article/Codebox/index.tsx new file mode 100644 index 0000000000000..e73f55ded326f --- /dev/null +++ b/components/Article/Codebox/index.tsx @@ -0,0 +1,83 @@ +import { useEffect, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { highlight, languages } from 'prismjs'; +import { sanitize } from 'isomorphic-dompurify'; +import classnames from 'classnames'; +import styles from './index.module.scss'; +import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; +import type { FC, PropsWithChildren, ReactElement, MouseEvent } from 'react'; + +type CodeBoxProps = { + children: ReactElement>; +}; + +export const replaceLabelLanguages = (language: string) => + language.replace(/console/i, 'bash'); + +export const replaceLanguages = (language: string) => + language + .replace(/mjs|cjs|javascript/i, 'js') + .replace(/console|shell/i, 'bash'); + +const Codebox: FC = ({ children: { props } }) => { + const [parsedCode, setParsedCode] = useState(''); + const [copied, copyText] = useCopyToClipboard(); + const [langIndex, setLangIndex] = useState(0); + + const className = props.className || 'text'; + + const languageOptions = className + .split('|') + .map(language => language.split('language-')[1]); + + const language = languageOptions[langIndex]; + + const codeArray = props.children + ? props.children.toString().split('--------------\n') + : ['']; + + const handleCopyCode = (event: MouseEvent) => { + event.preventDefault(); + copyText(codeArray[langIndex]); + }; + + useEffect(() => { + const parsedLanguage = replaceLanguages(language); + const prismLanguage = languages[parsedLanguage] || languages.text; + + setParsedCode( + sanitize(highlight(codeArray[langIndex], prismLanguage, parsedLanguage)) + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [langIndex]); + + return ( +
    +      
    +
    + {languageOptions.map((lang, index) => ( + + ))} +
    + +
    +
    +
    + ); +}; + +export default Codebox; diff --git a/components/Article/Codebox/light.module.scss b/components/Article/Codebox/light.module.scss new file mode 100644 index 0000000000000..8b35d68503fa0 --- /dev/null +++ b/components/Article/Codebox/light.module.scss @@ -0,0 +1,76 @@ +@mixin lightStyles { + background: var(--black2); + color: black; + + :global .token { + &.comment, + &.prolog, + &.doctype, + &.cdata { + color: slategray; + } + + &.namespace { + opacity: 0.7; + } + + &.property, + &.tag, + &.boolean, + &.number, + &.constant, + &.symbol, + &.deleted { + color: #905; + } + + &.selector, + &.attr-name, + &.char, + &.builtin, + &.inserted { + color: #690; + } + + &.entity, + &.url { + background: hsla(0, 0%, 100%, 0.5); + color: #9a6e3a; + } + + &.atrule, + &.attr-value, + &.keyword { + color: #07a; + } + + &.function, + &.class-name { + color: #dd4a68; + } + + &.regex, + &.important, + &.variable { + color: #e90; + } + + &.important, + &.bold { + font-weight: var(--font-weight-vold); + } + &.italic { + font-style: italic; + } + + &.entity { + cursor: help; + } + + &.punctuation, + &.operator, + &.string { + background-color: var(--black2); + } + } +} diff --git a/components/Article/EditLink/__snapshots__/index.stories.tsx.snap b/components/Article/EditLink/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..81565b24f4e3e --- /dev/null +++ b/components/Article/EditLink/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/EditLink Edit smoke-test 1`] = ` + +`; + +exports[`Article/EditLink Empty smoke-test 1`] = `""`; diff --git a/components/Article/EditLink/__tests__/__snapshots__/index.test.tsx.snap b/components/Article/EditLink/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 5e39c384a0b3e..0000000000000 --- a/components/Article/EditLink/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EditLink component edit mode renders correctly 1`] = ` - -`; - -exports[`EditLink component renders without a relative path 1`] = `
    `; - -exports[`EditLink component translate mode renders correctly 1`] = ` - -`; diff --git a/components/Article/EditLink/__tests__/index.test.tsx b/components/Article/EditLink/__tests__/index.test.tsx index cf0104a288114..8f762c18d6fc7 100644 --- a/components/Article/EditLink/__tests__/index.test.tsx +++ b/components/Article/EditLink/__tests__/index.test.tsx @@ -23,44 +23,7 @@ const i18nDataEditMode = { }, } as unknown as AppProps['i18nData']; -const i18nDataTranslateMode = { - currentLocale: { - code: 'xx', - }, - localeMessages: { - 'components.article.editLink.title.translate': - 'Interested to help with translations?', - }, -} as unknown as AppProps['i18nData']; - describe('EditLink component', () => { - it('edit mode renders correctly', () => { - const { container } = render( - - - - ); - expect(container).toMatchSnapshot(); - }); - - it('translate mode renders correctly', () => { - const { container } = render( - - - - ); - expect(container).toMatchSnapshot(); - }); - - it('renders without a relative path', () => { - const { container } = render( - - - - ); - expect(container).toMatchSnapshot(); - }); - it('produces correct relative path', () => { render( diff --git a/components/Article/InlineCode/__snapshots__/index.stories.tsx.snap b/components/Article/InlineCode/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..734dcbd2952be --- /dev/null +++ b/components/Article/InlineCode/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Article/InlineCode Default smoke-test 1`] = ` + + + const a = 1; + + +`; diff --git a/components/Article/InlineCode/index.module.scss b/components/Article/InlineCode/index.module.scss new file mode 100644 index 0000000000000..c674ce45deba1 --- /dev/null +++ b/components/Article/InlineCode/index.module.scss @@ -0,0 +1,36 @@ +@import '../../../styles/code'; + +.code { + @extend %codeBaseStyles; + + font-weight: var(--font-weight-light); + padding: 0 6px; + + white-space: break-spaces; +} + +[data-theme='light'] { + .code { + background-color: var(--black2); + color: var(--black9); + } + + a .code { + background-color: transparent; + color: var(--color-text-accent); + padding: 0; + } +} + +[data-theme='dark'] { + .code { + background-color: var(--black9); + color: var(--black2); + } + + a .code { + background-color: transparent; + color: var(--color-text-accent); + padding: 0; + } +} diff --git a/components/Article/InlineCode/index.stories.tsx b/components/Article/InlineCode/index.stories.tsx new file mode 100644 index 0000000000000..37d8503e65a52 --- /dev/null +++ b/components/Article/InlineCode/index.stories.tsx @@ -0,0 +1,20 @@ +import InlineCode from './index'; +import type { FC } from 'react'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +type DecoratedInlineCodeProps = { code: string }; + +const DecoratedInlineCode: FC = ({ code }) => ( + + {code} + +); + +type Story = StoryObj; +type Meta = MetaObj; + +const code = 'const a = 1;'; + +export const Default: Story = { args: { code } }; + +export default { component: DecoratedInlineCode } as Meta; diff --git a/components/Article/InlineCode/index.tsx b/components/Article/InlineCode/index.tsx new file mode 100644 index 0000000000000..a3f79197169a9 --- /dev/null +++ b/components/Article/InlineCode/index.tsx @@ -0,0 +1,8 @@ +import styles from './index.module.scss'; +import type { FC, PropsWithChildren } from 'react'; + +const InlineCode: FC = ({ children }) => ( + {children} +); + +export default InlineCode; diff --git a/components/Blog/BlogCard/__snapshots__/index.stories.tsx.snap b/components/Blog/BlogCard/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..3badfc810dc37 --- /dev/null +++ b/components/Blog/BlogCard/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Blog/BlogCard Default smoke-test 1`] = ` +
    + +
    +

    + April 21, 2023 +

    +

    + by + + Bat Man + +

    +
    +
    +`; diff --git a/components/Blog/BlogCard/__tests__/__snapshots__/index.test.tsx.snap b/components/Blog/BlogCard/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 430876477be63..0000000000000 --- a/components/Blog/BlogCard/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BlogCard component renders correctly 1`] = ` -
    -
    - -
    -

    - April 21, 2023 -

    -

    - components.blog.blogCard.author.by - - - Bat Man - -

    -
    -
    -
    -`; diff --git a/components/Blog/BlogCard/__tests__/index.test.tsx b/components/Blog/BlogCard/__tests__/index.test.tsx deleted file mode 100644 index 3a2b31ef998eb..0000000000000 --- a/components/Blog/BlogCard/__tests__/index.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { render } from '@testing-library/react'; -import { IntlProvider } from 'react-intl'; - -import BlogCard from '..'; - -jest.mock('next/router', () => ({ - useRouter: jest.fn().mockReturnValue({}), -})); - -jest.mock('../../../../hooks/useLocale', () => ({ - useLocale: jest.fn().mockReturnValue({ - currentLocale: {}, - }), -})); - -describe('BlogCard component', () => { - it('renders correctly', () => { - const { container } = render( - {}}> - - - ); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Common/AnimatedPlaceholder/__snapshots__/index.stories.tsx.snap b/components/Common/AnimatedPlaceholder/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..56afed2d696ce --- /dev/null +++ b/components/Common/AnimatedPlaceholder/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Common/AnimatedPlaceholder Default smoke-test 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +`; + +exports[`Common/AnimatedPlaceholder WithLoaderSkeleton smoke-test 1`] = ` +
    +
    +
    +
    +`; diff --git a/components/Common/AnimatedPlaceholder/__tests__/__snapshots__/index.test.tsx.snap b/components/Common/AnimatedPlaceholder/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index cdb93e8192bcb..0000000000000 --- a/components/Common/AnimatedPlaceholder/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AnimatedPlaceholder component should render correctly with default skeleton 1`] = ` -
    -
    -
    -
    -
    -
    -
    -
    -
    -`; - -exports[`AnimatedPlaceholder component should support passing loader skeleton from outside 1`] = ` -
    -
    -
    -
    -
    -`; diff --git a/components/Common/AnimatedPlaceholder/__tests__/index.test.tsx b/components/Common/AnimatedPlaceholder/__tests__/index.test.tsx deleted file mode 100644 index a6ed849a159e2..0000000000000 --- a/components/Common/AnimatedPlaceholder/__tests__/index.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { render } from '@testing-library/react'; -import AnimatedPlaceholder from './../index'; - -describe('AnimatedPlaceholder component', () => { - it('should render correctly with default skeleton', () => { - const { container } = render(); - - expect(container).toMatchSnapshot(); - }); - - it('should support passing loader skeleton from outside', () => { - const { container } = render( - -
    - - ); - - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Common/AnimatedPlaceholder/index.stories.tsx b/components/Common/AnimatedPlaceholder/index.stories.tsx index 5c5dda62aab11..1fe0db1e901d0 100644 --- a/components/Common/AnimatedPlaceholder/index.stories.tsx +++ b/components/Common/AnimatedPlaceholder/index.stories.tsx @@ -6,4 +6,10 @@ type Meta = MetaObj; export const Default: Story = {}; +export const WithLoaderSkeleton: Story = { + args: { + children:
    , + }, +}; + export default { component: AnimatedPlaceholder } as Meta; diff --git a/components/Common/Banner/__snapshots__/index.stories.tsx.snap b/components/Common/Banner/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..4197e73ba0427 --- /dev/null +++ b/components/Common/Banner/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Common/Banner WithHTML smoke-test 1`] = ` + +`; + +exports[`Common/Banner WithHTMLImage smoke-test 1`] = ` + +`; + +exports[`Common/Banner WithText smoke-test 1`] = ` + +`; diff --git a/components/Common/DarkModeToggle/__snapshots__/index.stories.tsx.snap b/components/Common/DarkModeToggle/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..e5116652b64f1 --- /dev/null +++ b/components/Common/DarkModeToggle/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Common/DarkModeToggle Default smoke-test 1`] = ` + +`; diff --git a/components/Common/DarkModeToggle/__tests__/__snapshots__/index.test.tsx.snap b/components/Common/DarkModeToggle/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 6f3759d5005b0..0000000000000 --- a/components/Common/DarkModeToggle/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,49 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DarkModeToggle Component render dark mode toggle 1`] = ` -
    - -
    -`; diff --git a/components/Common/DarkModeToggle/__tests__/index.test.tsx b/components/Common/DarkModeToggle/__tests__/index.test.tsx index 4122acad5f843..7675931a1ba85 100644 --- a/components/Common/DarkModeToggle/__tests__/index.test.tsx +++ b/components/Common/DarkModeToggle/__tests__/index.test.tsx @@ -17,15 +17,6 @@ jest.mock('next-themes', () => ({ })); describe('DarkModeToggle Component', () => { - it('render dark mode toggle', () => { - const { container } = render( - {}}> - - - ); - expect(container).toMatchSnapshot(); - }); - it('switches dark theme to light theme', async () => { const user = userEvent.setup(); mockCurrentTheme = 'dark'; diff --git a/components/Common/Dropdown/__snapshots__/index.stories.tsx.snap b/components/Common/Dropdown/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..99d2592ec46c7 --- /dev/null +++ b/components/Common/Dropdown/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Common/Dropdown withItems smoke-test 1`] = ` + +`; diff --git a/components/Common/LanguageSelector/__snapshots__/index.stories.tsx.snap b/components/Common/LanguageSelector/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..7aca05d4a2a97 --- /dev/null +++ b/components/Common/LanguageSelector/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,166 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Common/LanguageSelector Default smoke-test 1`] = ` +
    +
    + + +
    +
    +`; diff --git a/components/Common/LanguageSelector/index.tsx b/components/Common/LanguageSelector/index.tsx index e966f0f1cace6..4fbcfab57b3ce 100644 --- a/components/Common/LanguageSelector/index.tsx +++ b/components/Common/LanguageSelector/index.tsx @@ -1,9 +1,10 @@ -import { useMemo, useState } from 'react'; +import { useMemo, useState, useCallback } from 'react'; import { MdOutlineTranslate } from 'react-icons/md'; import { useIntl } from 'react-intl'; import styles from './index.module.scss'; import Dropdown from '../Dropdown'; import { useLocale } from '../../../hooks/useLocale'; +import { useClickOutside } from '../../../hooks/useClickOutside'; const dropdownStyle = { position: 'absolute', @@ -15,6 +16,9 @@ const dropdownStyle = { const LanguageSelector = () => { const [showDropdown, setShowDropdown] = useState(false); + const dropdownHandler = useCallback(() => setShowDropdown(false), []); + const ref = useClickOutside(dropdownHandler); + const { availableLocales, currentLocale } = useLocale(); const intl = useIntl(); @@ -37,7 +41,7 @@ const LanguageSelector = () => { }); return ( -
    +
    +`; diff --git a/components/Common/SectionTitle/__tests__/SectionTitle.test.tsx b/components/Common/SectionTitle/__tests__/SectionTitle.test.tsx index 3ea1f85e93947..ce6d27420c3b4 100644 --- a/components/Common/SectionTitle/__tests__/SectionTitle.test.tsx +++ b/components/Common/SectionTitle/__tests__/SectionTitle.test.tsx @@ -4,11 +4,6 @@ import SectionTitle from '..'; describe('SectionTitle component', () => { const mockData = ['home', 'previous', 'current']; - it('renders correctly with data', () => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - it('last item should be active', () => { render(); const active = screen.getByText(mockData[mockData.length - 1]); diff --git a/components/Common/SectionTitle/__tests__/__snapshots__/SectionTitle.test.tsx.snap b/components/Common/SectionTitle/__tests__/__snapshots__/SectionTitle.test.tsx.snap deleted file mode 100644 index b628f4982d33b..0000000000000 --- a/components/Common/SectionTitle/__tests__/__snapshots__/SectionTitle.test.tsx.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SectionTitle component renders correctly with data 1`] = ` -
    -
    - home / - previous / - - current - -
    -
    -`; diff --git a/components/Common/ShellBox/__snapshots__/index.stories.tsx.snap b/components/Common/ShellBox/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..d91a98fa53d3a --- /dev/null +++ b/components/Common/ShellBox/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,54 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Common/ShellBox Default smoke-test 1`] = ` +
    +  
    + + SHELL + + +
    + + echo hello world + +
    +`; + +exports[`Common/ShellBox WithTextToCopyJsx smoke-test 1`] = ` +
    +  
    + + SHELL + + +
    + + + + $ + + echo hello world + + +
    +`; + +exports[`Common/ShellBox WithoutTextToCopy smoke-test 1`] = ` +
    +  
    + + SHELL + + +
    + + echo hello world + +
    +`; diff --git a/components/Common/ShellBox/__tests__/__snapshots__/index.test.tsx.snap b/components/Common/ShellBox/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 0a18a22852632..0000000000000 --- a/components/Common/ShellBox/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ShellBox should render 1`] = ` -
    -
    -    
    - - SHELL - - -
    - - test - -
    -
    -`; diff --git a/components/Common/ShellBox/__tests__/index.test.tsx b/components/Common/ShellBox/__tests__/index.test.tsx index 9d764926a5d86..65c7dd3089aab 100644 --- a/components/Common/ShellBox/__tests__/index.test.tsx +++ b/components/Common/ShellBox/__tests__/index.test.tsx @@ -23,15 +23,6 @@ describe('ShellBox', () => { }); }); - it('should render', () => { - const { container } = render( - {}}> - test - - ); - expect(container).toMatchSnapshot(); - }); - it('should call clipboard API with `test` once', async () => { const user = userEvent.setup(); const navigatorClipboardWriteTextSpy = jest diff --git a/components/Home/Hero/__snapshots__/index.stories.tsx.snap b/components/Home/Hero/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..c007a451c16ac --- /dev/null +++ b/components/Home/Hero/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Home/Hero Default smoke-test 1`] = ` +
    +

    + Run JavaScript Everywhere. +

    +

    + Node.js is a free, open-sourced, cross-platform JavaScript run-time environment that lets developers write command line tools and server-side scripts outside of a browser. +

    + +
    +`; diff --git a/components/Home/Hero/__tests__/index.test.tsx b/components/Home/Hero/__tests__/index.test.tsx new file mode 100644 index 0000000000000..2c9152fd6b07c --- /dev/null +++ b/components/Home/Hero/__tests__/index.test.tsx @@ -0,0 +1,109 @@ +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from 'react-intl'; +import Hero from '..'; +import type { NodeReleaseData } from '../../../../types'; + +const mockNodeReleaseData: NodeReleaseData[] = [ + { + version: '14.17.0', + fullVersion: 'v14.17.0', + codename: 'Fermium', + isLts: true, + status: 'Maintenance LTS', + initialRelease: '2021-05-18', + ltsStart: '2020-10-27', + maintenanceStart: '2021-10-19', + endOfLife: '2023-04-01', + }, + { + version: '16.0.0', + fullVersion: 'v16.0.0', + codename: 'Gallium', + isLts: false, + status: 'Current', + initialRelease: '2021-04-20', + ltsStart: null, + maintenanceStart: null, + endOfLife: '2022-10-01', + }, +]; + +jest.mock('../../../../hooks/useDetectOS', () => ({ + useDetectOS: jest.fn().mockReturnValue({ + getDownloadLink: (version: string) => `/download/node/${version}`, + }), +})); + +describe('Hero component', () => { + it('renders the title', () => { + render( + {}}> + + + ); + expect(screen.getByText('Welcome to Node.js')).toBeInTheDocument(); + }); + + it('renders the subtitle when passed', () => { + render( + {}}> + + + ); + expect( + screen.getByText('Node.js is Javascript run-time environment') + ).toBeInTheDocument(); + }); + + it('renders the "Download LTS" button with the current LTS version', () => { + render( + {}}> + + + ); + const ltsDownloadButton = screen.getAllByRole('link')[0]; + expect(ltsDownloadButton).toHaveAttribute( + 'href', + '/download/node/v14.17.0' + ); + }); + + it('renders the "Get Current" link with the current version', () => { + render( + {}}> + + + ); + const currentVersionLink = screen.getAllByRole('link')[1]; + expect(currentVersionLink).toHaveAttribute( + 'href', + '/download/node/v16.0.0' + ); + }); + + it('renders the "Learn more" link', () => { + render( + {}}> + + + ); + const learnMoreLink = screen.getAllByRole('link')[2]; + expect(learnMoreLink).toHaveAttribute('href', '/learn'); + }); +}); diff --git a/components/Home/Hero/index.module.scss b/components/Home/Hero/index.module.scss new file mode 100644 index 0000000000000..566ea4a1cf162 --- /dev/null +++ b/components/Home/Hero/index.module.scss @@ -0,0 +1,110 @@ +@use '../../../styles/typography.scss'; + +.hero { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + padding: var(--space-96) 0 var(--space-128) 0; + text-align: center; + + h1, + p { + margin: 0; + } + + h1 { + max-width: 840px; + + @media (max-width: 900px) { + font-size: var(--font-size-display2); + line-height: 48px; + max-width: 90vw; + } + } + + .subTitle { + @extend .t-subheading; + + color: var(--color-text-secondary); + font-weight: var(--font-weight-light); + margin: var(--space-32) 0 var(--space-32) 0; + max-width: 780px; + + @media (max-width: 900px) { + max-width: 90vw; + } + } + + .buttonsContainer { + align-items: flex-start; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + + .downloadLtsContainer { + align-items: center; + display: flex; + flex-direction: column; + position: relative; + + & > p { + margin-top: var(--space-08); + } + + @media (max-width: 640px) { + margin-bottom: var(--space-20); + } + } + } +} + +.downloadButton { + align-items: center; + background-color: var(--brand6); + border-radius: 5rem; + box-sizing: border-box; + color: var(--black0); + display: flex; + flex-direction: column; + font-weight: var(--font-weight-semibold); + height: 5.6rem; + justify-content: center; + margin: 0 var(--space-16); + padding: 2.4rem; + text-decoration: none; + transition: background-color 0.2s ease-out; + + span { + color: var(--black4); + font-size: var(--font-size-body3); + } + + &:hover { + background-color: var(--brand7); + color: var(--black0); + } + + &:focus { + @extend .downloadButton; + } + + &Inverse { + @extend .downloadButton; + + background-color: transparent; + border: var(--brand6) var(--space-02) solid; + color: var(--brand6); + transition: background-color 0.2s ease-out; + + &:hover { + background-color: var(--brand7); + border: var(--brand7) var(--space-02) solid; + } + } +} + +[data-theme='dark'] .downloadButtonInverse { + color: var(--black0); +} diff --git a/components/Home/Hero/index.stories.tsx b/components/Home/Hero/index.stories.tsx new file mode 100644 index 0000000000000..8f7ce59940a8a --- /dev/null +++ b/components/Home/Hero/index.stories.tsx @@ -0,0 +1,39 @@ +import Hero from '.'; +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = { + args: { + title: 'Run JavaScript Everywhere.', + subTitle: + 'Node.js is a free, open-sourced, cross-platform JavaScript run-time environment that lets developers write command line tools and server-side scripts outside of a browser.', + nodeReleaseData: [ + { + version: '18', + fullVersion: 'v18.15.0', + codename: '', + isLts: true, + status: 'Active LTS', + initialRelease: '', + ltsStart: '', + maintenanceStart: null, + endOfLife: '', + }, + { + version: '19', + fullVersion: 'v19.8.1', + codename: '', + isLts: true, + status: 'Current', + initialRelease: '', + ltsStart: '', + maintenanceStart: null, + endOfLife: '', + }, + ], + }, +}; + +export default { component: Hero } as Meta; diff --git a/components/Home/Hero/index.tsx b/components/Home/Hero/index.tsx new file mode 100644 index 0000000000000..fd8cffd54aa4c --- /dev/null +++ b/components/Home/Hero/index.tsx @@ -0,0 +1,56 @@ +import Link from 'next/link'; +import { FormattedMessage } from 'react-intl'; +import styles from './index.module.scss'; +import { useDetectOS } from '../../../hooks/useDetectOS'; +import type { FC } from 'react'; +import type { NodeReleaseData } from '../../../types'; + +type HeroProps = { + title: string; + subTitle?: string; + nodeReleaseData: NodeReleaseData[]; +}; + +const Hero: FC = ({ title, subTitle, nodeReleaseData }) => { + const { getDownloadLink } = useDetectOS(); + + const currentLTS = nodeReleaseData.find(release => release.isLts); + const currentRelease = nodeReleaseData.find( + release => release.status === 'Current' + ); + + const ltsVersionUrl = getDownloadLink(currentLTS?.fullVersion || ''); + const currentVersionUrl = getDownloadLink(currentRelease?.fullVersion || ''); + return ( +
    +

    {title}

    +

    {subTitle}

    +
    + + + + +
    +
    + ); +}; + +export default Hero; diff --git a/components/Home/NodeFeatures/__snapshots__/index.stories.tsx.snap b/components/Home/NodeFeatures/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..0d98f06b69774 --- /dev/null +++ b/components/Home/NodeFeatures/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Home/NodeFeatures Default smoke-test 1`] = ` +
    +
    + + + + + + +

    + JavaScript +

    +

    + Node.js provides support for the JavaScript programming language +

    +
    +
    + + + + +

    + Open Source +

    +

    + Node.js is open source and actively maintained by contributors all over the world +

    +
    +
    + + + + + + +

    + Everywhere +

    +

    + Node.js has been adapted to work in a wide variety of places +

    +
    +
    +`; diff --git a/components/Home/NodeFeatures/__test__/__snapshots__/index.test.tsx.snap b/components/Home/NodeFeatures/__test__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 4cf67440b4440..0000000000000 --- a/components/Home/NodeFeatures/__test__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NodeFeatures should render 1`] = ` -
    -
    -
    - - - - -

    - components.home.nodeFeatures.javascript.title -

    -

    - components.home.nodeFeatures.javascript.description -

    -
    -
    - - - -

    - components.home.nodeFeatures.openSource.title -

    -

    - components.home.nodeFeatures.openSource.description -

    -
    -
    - - - - -

    - components.home.nodeFeatures.everywhere.title -

    -

    - components.home.nodeFeatures.everywhere.description -

    -
    -
    -
    -`; diff --git a/components/Home/NodeFeatures/__test__/index.test.tsx b/components/Home/NodeFeatures/__test__/index.test.tsx deleted file mode 100644 index 11fd0c5177601..0000000000000 --- a/components/Home/NodeFeatures/__test__/index.test.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { render } from '@testing-library/react'; -import { IntlProvider } from 'react-intl'; -import NodeFeatures from '../index'; - -describe('NodeFeatures', () => { - it('should render', () => { - const { container } = render( - {}}> - - - ); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/components/Learn/PreviousNextLink/__snapshots__/index.stories.tsx.snap b/components/Learn/PreviousNextLink/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..f33e37db309bb --- /dev/null +++ b/components/Learn/PreviousNextLink/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,96 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Learn/PreviousNextLink Default smoke-test 1`] = ` + +`; + +exports[`Learn/PreviousNextLink WithoutNext smoke-test 1`] = ` + +`; + +exports[`Learn/PreviousNextLink WithoutPrevious smoke-test 1`] = ` + +`; diff --git a/components/Sections/NewFooter/__snapshots__/index.stories.tsx.snap b/components/Sections/NewFooter/__snapshots__/index.stories.tsx.snap new file mode 100644 index 0000000000000..1491114d542ee --- /dev/null +++ b/components/Sections/NewFooter/__snapshots__/index.stories.tsx.snap @@ -0,0 +1,139 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Sections/NewFooter Default smoke-test 1`] = ` + +`; diff --git a/components/Sections/NewFooter/__tests__/__snapshots__/index.test.tsx.snap b/components/Sections/NewFooter/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 7f6cda4e1abbc..0000000000000 --- a/components/Sections/NewFooter/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,165 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Tests for Footer component renders correctly 1`] = ` - -`; diff --git a/components/Sections/NewFooter/__tests__/index.test.tsx b/components/Sections/NewFooter/__tests__/index.test.tsx deleted file mode 100644 index 7f003ffbd7d93..0000000000000 --- a/components/Sections/NewFooter/__tests__/index.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { render } from '@testing-library/react'; -import { IntlProvider } from 'react-intl'; -import Footer from '..'; - -// mock useRouter -jest.mock('next/router', () => ({ - useRouter() { - return { - locale: 'en', - }; - }, -})); - -jest.mock('../../../../hooks/useLocale', () => ({ - useLocale: () => ({ - currentLocale: { code: 'en', name: 'English', localName: 'English' }, - }), -})); - -describe('Tests for Footer component', () => { - it('renders correctly', () => { - const { container } = render( - {}}> -