diff --git a/e2e/blog.spec.ts b/e2e/blog.spec.ts index 1f180c284..29eb8e2aa 100644 --- a/e2e/blog.spec.ts +++ b/e2e/blog.spec.ts @@ -60,4 +60,42 @@ test.describe('blog', () => { await expect(page.locator('h1').first()).toHaveText('Blog'); } }); + + test('searches correctly', async () => { + const input = page.getByRole('textbox'); + + await input.fill('Vault'); + + const title = page.locator('[data-testid="post-title"]', { + hasText: 'Getting started with aws-vault', + }); + + await expect(title).toHaveText('Getting started with aws-vault'); + + const otherBlogPost = page.locator('[data-testid="post-title"]', { + hasText: 'Code linters and formatters', + }); + + await expect(otherBlogPost).not.toBeVisible(); + + await expect(page).toHaveURL(`${baseUrl}/blog?title=Vault`); + }); + + test('searches correctly via visting URL param', async () => { + await page.goto(`${baseUrl}/blog?title=playwright`); + const input = page.getByRole('textbox'); + + await expect(input).toHaveValue('playwright'); + + const playwrightBlogPost = page.locator('[data-testid="post-title"]', { + hasText: 'Getting started with Playwright UI testing', + }); + + await expect(playwrightBlogPost).toBeVisible(); + + const otherBlogPost = page.locator('[data-testid="post-title"]', { + hasText: 'Code linters and formatters', + }); + await expect(otherBlogPost).not.toBeVisible(); + }); }); diff --git a/next-env.d.ts b/next-env.d.ts index 40c3d6809..1b3be0840 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index cc754f945..ccbd5dede 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "next-themes": "^0.4.4", "nodemon": "^3.1.7", "nprogress": "^0.2.0", + "nuqs": "^2.2.3", "parse-numeric-range": "^1.3.0", "pino": "^9.5.0", "prism-react-renderer": "^2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40a378212..331a7416d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,7 +193,7 @@ importers: version: 5.0.0(@types/react@19.0.1)(acorn@8.14.0)(react@19.0.0) next-sanity: specifier: 9.8.25 - version: 9.8.25(@sanity/client@6.24.1)(@sanity/icons@3.5.2(react@19.0.0))(@sanity/types@3.66.1)(@sanity/ui@2.10.5(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react-is@18.3.1)(react@19.0.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sanity@3.66.1(@emotion/is-prop-valid@1.3.1)(@types/node@22.10.2)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@19.0.1)(encoding@0.1.13)(react@19.0.0))(react@19.0.0)(sass@1.82.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(terser@5.37.0))(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 9.8.25(@sanity/client@6.24.1)(@sanity/icons@3.5.2(react@19.0.0))(@sanity/types@3.66.1(debug@4.4.0))(@sanity/ui@2.10.5(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react-is@18.3.1)(react@19.0.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sanity@3.66.1(@emotion/is-prop-valid@1.3.1)(@types/node@22.10.2)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@19.0.1)(encoding@0.1.13)(react@19.0.0))(react@19.0.0)(sass@1.82.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(terser@5.37.0))(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) next-seo: specifier: ^6.6.0 version: 6.6.0(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -206,6 +206,9 @@ importers: nprogress: specifier: ^0.2.0 version: 0.2.0 + nuqs: + specifier: ^2.2.3 + version: 2.2.3(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react@19.0.0) parse-numeric-range: specifier: ^1.3.0 version: 1.3.0 @@ -425,7 +428,7 @@ importers: version: 8.57.0 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.2(eslint@8.57.0))(eslint@8.57.0) + version: 19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.2(eslint@8.57.0))(eslint@8.57.0) eslint-config-next: specifier: 15.1.0 version: 15.1.0(eslint@8.57.0)(typescript@5.7.2) @@ -434,7 +437,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-import-resolver-typescript: specifier: ^3.6.3 - version: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + version: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0) eslint-plugin-import: specifier: ^2.31.0 version: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) @@ -5384,9 +5387,6 @@ packages: '@types/node@16.18.11': resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} - '@types/node@22.10.1': - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} - '@types/node@22.10.2': resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} @@ -9851,6 +9851,9 @@ packages: resolution: {integrity: sha512-7PujJ3Te6GGg9lG1nfw5jYCPV6/BsoAT0nCQwb6w+ROuromXYxI6jc/CQSlD82Z/OUMSBX1SoaqhTE+vXiLQzQ==} engines: {node: '>=4.0.0'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -10130,6 +10133,21 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + nuqs@2.2.3: + resolution: {integrity: sha512-nMCcUW06KSqEXA0xp+LiRqDpIE59BVYbjZLe0HUisJAlswfihHYSsAjYTzV0lcE1thfh8uh+LqUHGdQ8qq8rfA==} + peerDependencies: + '@remix-run/react': '>=2' + next: '>=14.2.0' + react: '>=18.2.0 || ^19.0.0-0' + react-router-dom: '>=6' + peerDependenciesMeta: + '@remix-run/react': + optional: true + next: + optional: true + react-router-dom: + optional: true + nwsapi@2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} @@ -16447,7 +16465,7 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.10.1 + '@types/node': 22.10.2 '@types/yargs': 15.0.19 chalk: 4.1.2 optional: true @@ -18404,7 +18422,7 @@ snapshots: '@sanity/comlink': 2.0.1 '@sanity/icons': 3.5.2(react@19.0.0) '@sanity/logos': 2.1.13(@sanity/color@3.0.6)(react@19.0.0) - '@sanity/preview-url-secret': 2.0.5(@sanity/client@6.24.1) + '@sanity/preview-url-secret': 2.0.5(@sanity/client@6.24.1(debug@4.4.0)) '@sanity/ui': 2.10.5(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react-is@18.3.1)(react@19.0.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) '@sanity/uuid': 3.0.2 fast-deep-equal: 3.1.3 @@ -18440,7 +18458,7 @@ snapshots: optionalDependencies: react: 19.0.0 - '@sanity/preview-url-secret@2.0.5(@sanity/client@6.24.1)': + '@sanity/preview-url-secret@2.0.5(@sanity/client@6.24.1(debug@4.4.0))': dependencies: '@sanity/client': 6.24.1(debug@4.4.0) '@sanity/uuid': 3.0.2 @@ -18561,7 +18579,7 @@ snapshots: dependencies: '@sanity/comlink': 2.0.1 '@sanity/mutate': 0.11.0-canary.3(xstate@5.19.0) - '@sanity/preview-url-secret': 2.0.5(@sanity/client@6.24.1) + '@sanity/preview-url-secret': 2.0.5(@sanity/client@6.24.1(debug@4.4.0)) '@vercel/stega': 0.1.2 get-random-values-esm: 1.0.2 react: 19.0.0 @@ -18998,7 +19016,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.10.1 + '@types/node': 22.10.2 optional: true '@types/body-scroll-lock@3.1.2': {} @@ -19017,7 +19035,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 optional: true '@types/conventional-commits-parser@5.0.1': @@ -19052,7 +19070,7 @@ snapshots: '@types/express-serve-static-core@5.0.2': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -19167,14 +19185,10 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 '@types/node@16.18.11': {} - '@types/node@22.10.1': - dependencies: - undici-types: 6.20.0 - '@types/node@22.10.2': dependencies: undici-types: 6.20.0 @@ -19237,13 +19251,13 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.10.1 + '@types/node': 22.10.2 optional: true '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.10.1 + '@types/node': 22.10.2 '@types/send': 0.17.4 optional: true @@ -20531,7 +20545,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -20542,7 +20556,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -21748,7 +21762,7 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0): dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 @@ -21757,10 +21771,10 @@ snapshots: object.entries: 1.1.7 semver: 6.3.1 - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.2(eslint@8.57.0))(eslint@8.57.0): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.0))(eslint-plugin-react@7.37.2(eslint@8.57.0))(eslint@8.57.0): dependencies: eslint: 8.57.0 - eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.2(eslint@8.57.0) @@ -21806,7 +21820,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -21819,7 +21833,26 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.3.7(supports-color@5.5.0) + enhanced-resolve: 5.17.1 + eslint: 8.57.0 + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -21830,14 +21863,24 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.12.2(eslint@8.57.0)(typescript@5.7.2) + eslint: 8.57.0 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.12.2(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -21852,7 +21895,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -23732,7 +23775,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.10.1 + '@types/node': 22.10.2 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -25008,6 +25051,8 @@ snapshots: stream-each: 1.2.3 through2: 3.0.2 + mitt@3.0.1: {} + mkdirp-classic@0.5.3: {} mkdirp@0.5.6: @@ -25098,14 +25143,14 @@ snapshots: next: 15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0) react: 19.0.0 - next-sanity@9.8.25(@sanity/client@6.24.1)(@sanity/icons@3.5.2(react@19.0.0))(@sanity/types@3.66.1)(@sanity/ui@2.10.5(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react-is@18.3.1)(react@19.0.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sanity@3.66.1(@emotion/is-prop-valid@1.3.1)(@types/node@22.10.2)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@19.0.1)(encoding@0.1.13)(react@19.0.0))(react@19.0.0)(sass@1.82.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(terser@5.37.0))(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): + next-sanity@9.8.25(@sanity/client@6.24.1)(@sanity/icons@3.5.2(react@19.0.0))(@sanity/types@3.66.1(debug@4.4.0))(@sanity/ui@2.10.5(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react-is@18.3.1)(react@19.0.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sanity@3.66.1(@emotion/is-prop-valid@1.3.1)(@types/node@22.10.2)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@19.0.1)(encoding@0.1.13)(react@19.0.0))(react@19.0.0)(sass@1.82.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(terser@5.37.0))(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: '@portabletext/react': 3.2.0(react@19.0.0) '@sanity/client': 6.24.1(debug@4.4.0) '@sanity/icons': 3.5.2(react@19.0.0) '@sanity/next-loader': 1.2.3(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react@19.0.0) '@sanity/preview-kit': 5.1.23(@sanity/client@6.24.1)(react@19.0.0) - '@sanity/preview-url-secret': 2.0.5(@sanity/client@6.24.1) + '@sanity/preview-url-secret': 2.0.5(@sanity/client@6.24.1(debug@4.4.0)) '@sanity/types': 3.66.1(debug@4.4.0) '@sanity/ui': 2.10.5(@emotion/is-prop-valid@1.3.1)(react-dom@19.0.0(react@19.0.0))(react-is@18.3.1)(react@19.0.0)(styled-components@6.1.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) '@sanity/visual-editing': 2.10.5(@sanity/client@6.24.1)(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -25286,6 +25331,13 @@ snapshots: nullthrows@1.1.1: {} + nuqs@2.2.3(next@15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0))(react@19.0.0): + dependencies: + mitt: 3.0.1 + react: 19.0.0 + optionalDependencies: + next: 15.1.0(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.82.0) + nwsapi@2.2.7: {} ob1@0.81.0: diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 90db80d2f..888311496 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -91,7 +91,6 @@ export default async function AboutPage() { ))} - diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index 901921e63..d6c63030c 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -50,6 +50,10 @@ export default async function PostPage({ params }: Props) { placeholder="blur" blurDataURL={imageService.urlFor(post.image.asset) ?? undefined} alt={post.image.alt ?? post.title} + style={{ + maxWidth: '100%', + height: 'auto', + }} /> { + test('renders posts', async () => { + mockUseSearchParams.mockReturnValue(new ReadonlyURLSearchParams('/blog')); + + render(); + + expect(screen.getByText(posts[0].title)).toBeInTheDocument(); + expect(screen.getByText(posts[0].intro)).toBeInTheDocument(); + + expect(screen.getByText(posts[1].title)).toBeInTheDocument(); + expect(screen.getByText(posts[1].intro)).toBeInTheDocument(); + + expect(screen.getByText(posts[2].title)).toBeInTheDocument(); + expect(screen.getByText(posts[2].intro)).toBeInTheDocument(); + }); + + test('typing in input adds to query param and filters posts', async () => { + const push = jest.fn(); + + // @ts-expect-error - we don't need to mock all the properties but TS isn't happy about that + mockUseRouter.mockReturnValue({ push }); + + mockUseSearchParams.mockReturnValue(new ReadonlyURLSearchParams('/blog')); + + render(); + + expect(screen.getByText(posts[0].title)).toBeInTheDocument(); + expect(screen.getByText(posts[0].intro)).toBeInTheDocument(); + + expect(screen.getByText(posts[1].title)).toBeInTheDocument(); + expect(screen.getByText(posts[1].intro)).toBeInTheDocument(); + + expect(screen.getByText(posts[2].title)).toBeInTheDocument(); + expect(screen.getByText(posts[2].intro)).toBeInTheDocument(); + + await userEvent.type(screen.getByRole('textbox'), 'vault'); + + // vault post + expect(screen.queryByText(posts[2].title)).toBeInTheDocument(); + expect(screen.queryByText(posts[2].intro)).toBeInTheDocument(); + + // rest of posts + expect(screen.queryByText(posts[0].title)).not.toBeInTheDocument(); + expect(screen.queryByText(posts[0].intro)).not.toBeInTheDocument(); + + expect(screen.queryByText(posts[1].title)).not.toBeInTheDocument(); + expect(screen.queryByText(posts[1].intro)).not.toBeInTheDocument(); + }); +}); diff --git a/src/app/blog/page.client.tsx b/src/app/blog/page.client.tsx new file mode 100644 index 000000000..c1efe1c9b --- /dev/null +++ b/src/app/blog/page.client.tsx @@ -0,0 +1,112 @@ +'use client'; + +import Box from '@frontend/components/Box'; +import Heading from '@frontend/components/Heading'; +import Input from '@frontend/components/Input'; +import PostItem from '@frontend/components/PostItem'; +import Spacer from '@frontend/components/Spacer'; +import { Post } from '@frontend/types/sanity'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { ChangeEvent, useCallback, useState } from 'react'; + +interface Props { + posts: Post[]; +} + +export default function PostsClient({ posts }: Props) { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const [query, setQuery] = useState({ + title: searchParams.get('title') || '', + }); + + const createQueryString = useCallback( + (name: string, value: string) => { + const params = new URLSearchParams(searchParams.toString()); + if (value) { + params.set(name, value); + } else { + params.delete(name); + } + + return params.toString(); + }, + [searchParams], + ); + + const handleInputChange = (e: ChangeEvent) => { + const { name, value } = e.target; + + setQuery(prevState => ({ + ...prevState, + [name]: value, + })); + + const queryString = createQueryString(name, value); + + router.push(`${pathname}?${queryString}`); + }; + + const filteredPosts = posts + .filter(post => { + return post.title.toLowerCase().includes(query.title.toLowerCase()); + }) + .sort((a, b) => { + if (a.publishedAt < b.publishedAt) { + return 1; + } + + if (a.publishedAt > b.publishedAt) { + return -1; + } + + return 0; + }); + + const postsByYear: Record = {}; + + filteredPosts.forEach(post => { + const year = new Date(post.publishedAt).getFullYear(); + + if (!postsByYear[year]) { + postsByYear[year] = []; + } + + postsByYear[year].push(post); + }); + + const sortedYears = Object.keys(postsByYear).sort( + (a, b) => Number(b) - Number(a), + ); + + return ( + <> + + + + + + + {sortedYears.map(year => ( + + + {year} + + + {postsByYear[year].map(post => ( + + ))} + + ))} + + + ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index f4f2542b9..ea679374b 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -1,12 +1,12 @@ import Box from '@frontend/components/Box'; import Heading from '@frontend/components/Heading'; import Page from '@frontend/components/Page'; -import PostItem from '@frontend/components/PostItem'; import Spacer from '@frontend/components/Spacer'; import Text from '@frontend/components/Text'; import postService from '@frontend/services/postService'; -import { Post } from '@frontend/types/sanity'; import { Metadata } from 'next'; +import { Suspense } from 'react'; +import PostsClient from './page.client'; export const revalidate = 1800; @@ -19,34 +19,6 @@ export const metadata: Metadata = { export default async function BlogPage() { const posts = await postService.getAllPosts(); - const allPosts = posts.sort((a, b) => { - if (a.publishedAt < b.publishedAt) { - return 1; - } - - if (a.publishedAt > b.publishedAt) { - return -1; - } - - return 0; - }); - - const postsByYear: Record = {}; - - allPosts.forEach(post => { - const year = new Date(post.publishedAt).getFullYear(); - - if (!postsByYear[year]) { - postsByYear[year] = []; - } - - postsByYear[year].push(post); - }); - - const sortedYears = Object.keys(postsByYear).sort( - (a, b) => Number(b) - Number(a), - ); - return ( @@ -59,19 +31,9 @@ export default async function BlogPage() { - - {sortedYears.map(year => ( - - - {year} - - - {postsByYear[year].map(post => ( - - ))} - - ))} - + + + ); } diff --git a/src/app/projects/[slug]/page.tsx b/src/app/projects/[slug]/page.tsx index fa009cba8..027063431 100644 --- a/src/app/projects/[slug]/page.tsx +++ b/src/app/projects/[slug]/page.tsx @@ -49,6 +49,10 @@ export default async function ProjectPage({ params }: Props) { placeholder="blur" blurDataURL={imageService.urlFor(project.image.asset)} alt={project.image.alt ?? project.title} + style={{ + maxWidth: '100%', + height: 'auto', + }} /> ; + +export default function Input(props: InputProps) { + return ; +} diff --git a/src/components/NowPlaying/index.tsx b/src/components/NowPlaying/index.tsx index 70c5465b8..68c6057ca 100644 --- a/src/components/NowPlaying/index.tsx +++ b/src/components/NowPlaying/index.tsx @@ -37,11 +37,12 @@ export default function NowPlaying() { blurDataURL={data.albumImageUrl} placeholder="blur" alt="Album cover" - layout="intrinsic" width={65} height={65} style={{ borderRadius: '7px', + maxWidth: '100%', + height: 'auto', }} />

diff --git a/src/components/ProjectItem/index.tsx b/src/components/ProjectItem/index.tsx index 8d65a7526..6e8315adf 100644 --- a/src/components/ProjectItem/index.tsx +++ b/src/components/ProjectItem/index.tsx @@ -57,7 +57,6 @@ export default function ProjectItem({ project }: Props) {
- { height={300} style={{ marginBottom: variables.spacing.sm, + maxWidth: '100%', + height: 'auto', }} /> )} diff --git a/src/test/appRouterMock.tsx b/src/test/appRouterMock.tsx new file mode 100644 index 000000000..be3726d09 --- /dev/null +++ b/src/test/appRouterMock.tsx @@ -0,0 +1,33 @@ +// app-router-context-provider-mock.tsx + +import { + AppRouterContext, + AppRouterInstance, +} from 'next/dist/shared/lib/app-router-context.shared-runtime'; +import React from 'react'; + +export type AppRouterContextProviderMockProps = { + router: Partial; + children: React.ReactNode; +}; + +export const AppRouterContextProviderMock = ({ + router, + children, +}: AppRouterContextProviderMockProps): React.ReactNode => { + // eslint-disable-next-line react/jsx-no-constructed-context-values + const mockedRouter: AppRouterInstance = { + back: jest.fn(), + forward: jest.fn(), + push: jest.fn(), + replace: jest.fn(), + refresh: jest.fn(), + prefetch: jest.fn(), + ...router, + }; + return ( + + {children} + + ); +}; diff --git a/test/setupGlobals.js b/test/setupGlobals.js index 8b1e831ad..b4ea870c1 100644 --- a/test/setupGlobals.js +++ b/test/setupGlobals.js @@ -10,7 +10,7 @@ const trueForNonBoolProp2 = /Warning: Received*/; const radixUiDialog = /`DialogContent` requires a `DialogTitle` for the component to be accessible for screen reader users./; const radixUiDialogWarn = - /Warning: Missing `Description` or `aria-describedby={undefined}` for {DialogContent}./; + /Warning: Missing `Description` or `aria-describedby={undefined}` for {DialogContent}*/; beforeAll(() => { console.error = (...args) => { @@ -19,14 +19,20 @@ beforeAll(() => { forwardRefWarning.test(args[0]) || trueForNonBoolProp.test(args[0]) || trueForNonBoolProp2.test(args[0]) || - radixUiDialog.test(args[0]) || - radixUiDialogWarn.test(args[0]) + radixUiDialog.test(args[0]) ) { return; } originalError.call(console, ...args); }; + console.warn = (...args) => { + if (radixUiDialogWarn.test(args[0])) { + return; + } + originalError.call(console, ...args); + }; + Element.prototype.scrollIntoView = jest.fn(); window.scroll = jest.fn(); diff --git a/test/setupTests.js b/test/setupTests.js index 76ab6b555..e67dfca16 100644 --- a/test/setupTests.js +++ b/test/setupTests.js @@ -13,24 +13,12 @@ jest.mock('nanoid', () => { }; }); -// jest.mock('next/navigation', () => ({ -// __esModule: true, -// useRouter: () => ({ -// push: jest.fn(), -// replace: jest.fn(), -// prefetch: jest.fn(), -// isFallback: false, -// }), -// useSearchParams: () => ({ -// get: () => {}, -// }), -// })); - if (typeof window !== 'undefined') { // fetch polyfill for making API calls. // eslint-disable-next-line @typescript-eslint/no-require-imports require('cross-fetch'); } + global.Request = jest.requireActual('node-fetch').Request; global.Response = jest.requireActual('node-fetch').Response;