Break up large test suites (#50458)

This breaks up some of our longest running tests which allows more
parallelizing of the tests. This also moves turbopack tests filtering
back to an allow list as it is running a lot of unrelated tests
currently which isn't ideal. We should only be running against tests
that are explicitly testing turbopack e.g. build tests should not be
duplicated in the turbopack group.

```sh
test/integration/css/test/group-1.test.js: 762.655
test/integration/edge-runtime-module-errors/test/index.test.js: 695.309
test/integration/css/test/group-2.test.js: 671.848
test/integration/i18n-support/test/index.test.js: 518.173
test/integration/scss-modules/test/index.test.js: 451.704
test/integration/css-features/test/index.test.js: 417.318
test/integration/css-modules/test/index.test.js: 403.405
test/integration/eslint/test/index.test.js: 381.563
test/integration/500-page/test/index.test.js: 371.134
test/integration/telemetry/test/index.test.js: 367.691
test/development/acceptance-app/ReactRefreshLogBox.test.ts: 335.878
test/integration/create-next-app/templates.test.ts: 334.01
test/integration/scss/test/group-2.test.js: 327.255
test/integration/scss/test/group-1.test.js: 318.574
test/integration/edge-runtime-configurable-guards/test/index.test.js: 313.834
test/e2e/instrumentation-hook/instrumentation-hook.test.ts: 294.618
test/development/acceptance-app/error-recovery.test.ts: 283.355
test/e2e/app-dir/app/vercel-speed-insights.test.ts: 278.242
test/integration/create-next-app/index.test.ts: 272.442
```
This commit is contained in:
JJ Kasper 2023-05-28 13:59:41 -07:00 committed by GitHub
parent 8e202610a4
commit 29c2e89bd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 5814 additions and 5682 deletions

View file

@ -655,7 +655,7 @@ jobs:
name: next-swc-test-binary
path: packages/next-swc/native
- run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && NEXT_SWC_DEV_BIN=1 NEXT_TEST_JOB=1 NEXT_TEST_CNA=1 xvfb-run node run-tests.js test/integration/create-next-app/index.test.ts test/integration/create-next-app/templates.test.ts >> /proc/1/fd/1"
- run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && NEXT_SWC_DEV_BIN=1 NEXT_TEST_JOB=1 NEXT_TEST_CNA=1 xvfb-run node run-tests.js test/integration/create-next-app/*.test.ts >> /proc/1/fd/1"
if: ${{ needs.build.outputs.docsChange == 'nope' }}
- name: Upload test trace

View file

@ -1,478 +1,269 @@
// Tests that are currently disabled with Turbopack in CI.
// Any tests not listed in here are assumed to be enabled.
const disabledTests = [
// these are build specific
'test/integration/auto-export-error-bail/test/index.test.js',
'test/integration/auto-export-query-error/test/index.test.js',
// these are build specific
'test/integration/build-output/test/index.test.js',
'test/integration/build-trace-extra-entries-turbo/test/index.test.js',
'test/integration/build-trace-extra-entries/test/index.test.js',
'test/integration/build-warnings/test/index.test.js',
// next build specific
'test/integration/config-schema-check/test/index.test.js',
'test/integration/config-syntax-error/test/index.test.js',
'test/integration/config-validation/test/index.test.ts',
'test/integration/conflicting-ssg-paths/test/index.test.js',
// these are already tested against turbopack explicitly in the suite
'test/integration/create-next-app/index.test.ts',
'test/integration/create-next-app/templates.test.ts',
// next build specific
'test/integration/critical-css/test/index.test.js',
// custom-server isn't compatible with turbopack why is it here?
'test/integration/custom-server-types/test/index.test.js',
// Clean dist dir is a production only test should it be here?
'test/integration/clean-distdir/test/index.test.js',
// this is next build specific why is it here?
'test/integration/dist-dir/test/index.test.js',
// these are next build specific
'test/integration/export-404/test/index.test.js',
'test/integration/export-default-map/test/index.test.js',
'test/integration/export-dynamic-pages/test/index.test.js',
'test/integration/export-fallback-true-error/test/index.test.js',
'test/integration/export-getInitialProps-warn/test/index.test.js',
'test/integration/export-image-default/test/index.test.js',
'test/integration/export-image-loader-legacy/test/index.test.js',
'test/integration/export-image-loader/test/index.test.js',
'test/integration/export-index-not-found-gsp/test/index.test.ts',
'test/integration/export-intent/test/index.test.js',
'test/integration/export-no-build/test/index.test.js',
'test/integration/export-progress-status-message/test/index.test.js',
'test/integration/export-subfolders/test/index.test.js',
// build specific
'test/integration/middleware-build-errors/test/index.test.js',
'test/integration/webpack-require-hook/test/index.test.js',
// this is next build and next export specific
'test/integration/with-electron/test/index.test.js',
'test/e2e/test-template/{{ toFileName name }}/{{ toFileName name }}.test.ts',
'test/development/acceptance-app/app-hmr-changes.test.ts',
'test/development/acceptance-app/component-stack.test.ts',
'test/development/acceptance-app/editor-links.test.ts',
'test/development/acceptance-app/error-message-url.test.ts',
'test/development/acceptance-app/error-recovery.test.ts',
'test/development/acceptance-app/hydration-error.test.ts',
'test/development/acceptance-app/invalid-imports.test.ts',
'test/development/acceptance-app/ReactRefresh.test.ts',
'test/development/acceptance-app/ReactRefreshLogBox-builtins.test.ts',
'test/development/acceptance-app/ReactRefreshLogBox-scss.test.ts',
'test/development/acceptance-app/ReactRefreshLogBox.test.ts',
'test/development/acceptance-app/ReactRefreshModule.test.ts',
'test/development/acceptance-app/ReactRefreshRegression.test.ts',
'test/development/acceptance-app/rsc-build-errors.test.ts',
'test/development/acceptance-app/server-components.test.ts',
'test/development/acceptance-app/version-staleness.test.ts',
'test/development/acceptance/component-stack.test.ts',
'test/development/acceptance/error-recovery.test.ts',
'test/development/acceptance/hydration-error.test.ts',
'test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts',
'test/development/acceptance/ReactRefreshLogBox-builtins.test.ts',
'test/development/acceptance/ReactRefreshLogBox.test.ts',
'test/development/acceptance/ReactRefreshModule.test.ts',
'test/development/acceptance/ReactRefreshRegression.test.ts',
'test/development/acceptance/ReactRefreshRequire.test.ts',
'test/development/acceptance/server-component-compiler-errors-in-pages.test.ts',
'test/development/api-route-errors/index.test.ts',
'test/development/app-render-error-log/app-render-error-log.test.ts',
'test/development/basic/gssp-ssr-change-reloading/test/index.test.ts',
'test/development/basic/hmr.test.ts',
'test/development/basic/misc.test.ts',
'test/development/basic/next-dynamic.test.ts',
'test/development/basic/node-builtins.test.ts',
'test/development/basic/project-directory-rename.test.ts',
'test/development/basic/styled-components.test.ts',
'test/development/client-dev-overlay/index.test.ts',
'test/development/correct-tsconfig-defaults/index.test.ts',
'test/development/gssp-notfound/index.test.ts',
'test/development/next-font/build-errors.test.ts',
'test/development/next-font/deprecated-package.test.ts',
'test/development/next-font/font-loader-in-document-error.test.ts',
// below test times out
// Tests that are currently enabled with Turbopack in CI.
// Only tests that are actively testing against Turbopack should
// be enabled here
const enabledTests = [
'test/development/acceptance-app/ReactRefreshLogBoxMisc.test.ts',
'test/development/acceptance-app/ReactRefreshRequire.test.ts',
'test/development/acceptance-app/dynamic-error.test.ts',
// 'test/development/acceptance/ReactRefresh.test.ts',
'test/development/acceptance/ReactRefreshLogBox-scss.test.ts',
'test/development/acceptance/ReactRefreshLogBoxMisc.test.ts',
'test/development/api-cors-with-rewrite/index.test.ts',
'test/development/app-dir/multiple-compiles-single-route/multiple-compiles-single-route.test.ts',
'test/development/app-hmr/hmr.test.ts',
'test/development/basic/define-class-fields.test.ts',
'test/development/basic/emotion-swc.test.ts',
'test/development/basic/legacy-decorators.test.ts',
'test/development/basic/styled-components-disabled.test.ts',
'test/development/basic/tailwind-jit.test.ts',
'test/development/basic/theme-ui.test.ts',
'test/development/dotenv-default-expansion/index.test.ts',
'test/development/jsconfig-path-reloading/index.test.ts',
'test/development/middleware-warnings/index.test.ts',
'test/development/project-directory-with-styled-jsx-suffix/index.test.ts',
'test/development/repeated-dev-edits/repeated-dev-edits.test.ts',
'test/development/watch-config-file/index.test.ts',
'test/development/webpack-issuer-deprecation-warning/index.test.ts',
'test/e2e/404-page-router/index.test.ts',
'test/e2e/app-dir-legacy-edge-runtime-config/index.test.ts',
'test/e2e/app-dir/actions/app-action.test.ts',
'test/e2e/app-dir/app-a11y/index.test.ts',
'test/e2e/app-dir/app-basepath/index.test.ts',
'test/e2e/app-dir/app-css/index.test.ts',
'test/e2e/app-dir/app-edge/app-edge.test.ts',
'test/e2e/app-dir/app-middleware/app-middleware.test.ts',
'test/e2e/app-dir/app-rendering/rendering.test.ts',
'test/e2e/app-dir/app-routes-trailing-slash/app-routes-trailing-slash.test.ts',
'test/e2e/app-dir/app-routes/app-custom-routes.test.ts',
'test/e2e/app-dir/app-static/app-static-custom-handler.test.ts',
'test/e2e/app-dir/app-static/app-static.test.ts',
'test/e2e/app-dir/app/index.test.ts',
'test/e2e/app-dir/create-root-layout/create-root-layout.test.ts',
'test/e2e/app-dir/crypto-globally-available/crypto-globally-available.test.ts',
'test/e2e/app-dir/draft-mode/draft-mode-edge.test.ts',
'test/e2e/app-dir/draft-mode/draft-mode-node.test.ts',
'test/e2e/app-dir/dynamic-href/dynamic-href.test.ts',
'test/e2e/app-dir/edge-runtime-node-compatibility/edge-runtime-node-compatibility.test.ts',
'test/e2e/app-dir/error-boundary-and-not-found-linking/error-boundary-and-not-found-linking.test.ts',
'test/e2e/app-dir/front-redirect-issue/front-redirect-issue.test.ts',
'test/e2e/app-dir/hooks/hooks.test.ts',
'test/e2e/app-dir/i18n-hybrid/i18n-hybrid.test.js',
'test/e2e/app-dir/interception-middleware-rewrite/interception-middleware-rewrite.test.ts',
'test/e2e/app-dir/mdx/mdx.test.ts',
'test/e2e/app-dir/metadata-dynamic-routes/index.test.ts',
'test/e2e/app-dir/metadata/metadata.test.ts',
'test/e2e/app-dir/next-font/next-font.test.ts',
'test/e2e/app-dir/next-image/next-image.test.ts',
'test/e2e/app-dir/pages-to-app-routing/pages-to-app-routing.test.ts',
'test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts',
'test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts',
'test/e2e/app-dir/root-layout-redirect/root-layout-redirect.test.ts',
'test/e2e/app-dir/root-layout/root-layout.test.ts',
'test/development/tsconfig-path-reloading/index.test.ts',
'test/development/typescript-auto-install/index.test.ts',
'test/e2e/app-dir/_allow-underscored-root-directory/_allow-underscored-root-directory.test.ts',
'test/e2e/app-dir/actions/app-action-export.test.ts',
'test/e2e/app-dir/app-alias/app-alias.test.ts',
'test/e2e/app-dir/app-client-cache/client-cache.test.ts',
'test/e2e/app-dir/app-css-pageextensions/index.test.ts',
'test/e2e/app-dir/app-external/app-external.test.ts',
'test/e2e/app-dir/app-prefetch/prefetching.test.ts',
'test/e2e/app-dir/app-validation/validation.test.ts',
'test/e2e/app-dir/app/standalone.test.ts',
'test/e2e/app-dir/app/useReportWebVitals.test.ts',
'test/e2e/app-dir/app/vercel-speed-insights.test.ts',
'test/e2e/app-dir/asset-prefix/asset-prefix.test.ts',
'test/e2e/app-dir/async-component-preload/async-component-preload.test.ts',
'test/e2e/app-dir/autoscroll-with-css-modules/index.test.ts',
'test/e2e/app-dir/back-button-download-bug/back-button-download-bug.test.ts',
'test/e2e/app-dir/dynamic/dynamic.test.ts',
'test/e2e/app-dir/global-error/global-error.test.ts',
'test/e2e/app-dir/import/import.test.ts',
'test/e2e/app-dir/interpolability-with-pages/navigation.test.ts',
'test/e2e/app-dir/layout-params/layout-params.test.ts',
'test/e2e/app-dir/metadata-missing-metadata-base/index.test.ts',
'test/e2e/app-dir/metadata-suspense/index.test.ts',
'test/e2e/app-dir/navigation/navigation.test.ts',
'test/e2e/app-dir/not-found/not-found.test.ts',
'test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts',
'test/e2e/app-dir/route-page-manifest-bug/route-page-manifest-bug.test.ts',
'test/e2e/app-dir/router-autoscroll/router-autoscroll.test.ts',
'test/e2e/app-dir/router-stuck-dynamic-static-segment/router-stuck-dynamic-static-segment.test.ts',
'test/e2e/app-dir/rsc-basic/rsc-basic.test.ts',
'test/e2e/app-dir/set-cookies/set-cookies.test.ts',
'test/e2e/app-dir/trailingslash/trailingslash.test.ts',
'test/e2e/app-dir/with-babel/with-babel.test.ts',
'test/e2e/basepath-trailing-slash.test.ts',
'test/e2e/basepath.test.ts',
'test/e2e/browserslist/browserslist.test.ts',
'test/e2e/browserslist/legacybrowsers-false.test.ts',
'test/e2e/browserslist/legacybrowsers-true.test.ts',
'test/e2e/edge-can-read-request-body/index.test.ts',
'test/e2e/edge-can-use-wasm-files/index.test.ts',
'test/e2e/edge-compiler-can-import-blob-assets/index.test.ts',
'test/e2e/edge-configurable-runtime/index.test.ts',
'test/e2e/edge-pages-support/index.test.ts',
'test/e2e/fetch-failures-have-good-stack-traces-in-edge-runtime/fetch-failures-have-good-stack-traces-in-edge-runtime.test.ts',
'test/e2e/getserversideprops/test/index.test.ts',
'test/e2e/i18n-api-support/index.test.ts',
'test/e2e/i18n-data-fetching-redirect/index.test.ts',
'test/e2e/i18n-default-locale-redirect/i18n-default-locale-redirect.test.ts',
'test/e2e/i18n-disallow-multiple-locales/i18n-disallow-multiple-locales.test.ts',
'test/e2e/i18n-ignore-redirect-source-locale/redirects-with-basepath.test.ts',
'test/e2e/i18n-ignore-redirect-source-locale/redirects.test.ts',
'test/e2e/i18n-ignore-rewrite-source-locale/rewrites-with-basepath.test.ts',
'test/e2e/i18n-ignore-rewrite-source-locale/rewrites.test.ts',
'test/e2e/ignore-invalid-popstateevent/with-i18n.test.ts',
'test/e2e/instrumentation-hook-src/instrumentation-hook-src.test.ts',
'test/e2e/instrumentation-hook/instrumentation-hook.test.ts',
'test/e2e/manual-client-base-path/index.test.ts',
'test/e2e/middleware-custom-matchers-basepath/test/index.test.ts',
'test/e2e/middleware-custom-matchers-i18n/test/index.test.ts',
'test/e2e/middleware-custom-matchers/test/index.test.ts',
'test/e2e/middleware-dynamic-basepath-matcher/test/index.test.ts',
'test/e2e/middleware-fetches-with-any-http-method/index.test.ts',
'test/e2e/middleware-fetches-with-body/index.test.ts',
'test/e2e/middleware-matcher/index.test.ts',
'test/e2e/middleware-redirects/test/index.test.ts',
'test/e2e/middleware-request-header-overrides/test/index.test.ts',
'test/e2e/middleware-rewrites/test/index.test.ts',
'test/e2e/middleware-trailing-slash/test/index.test.ts',
'test/e2e/next-font/basepath.test.ts',
'test/e2e/next-font/google-fetch-error.test.ts',
'test/e2e/next-font/index.test.ts',
'test/e2e/next-font/with-font-declarations-file.test.ts',
'test/e2e/next-font/with-proxy.test.ts',
'test/e2e/next-font/without-preloaded-fonts.test.ts',
'test/e2e/next-head/index.test.ts',
'test/e2e/next-script/index.test.ts',
'test/e2e/og-api/index.test.ts',
'test/e2e/opentelemetry/opentelemetry.test.ts',
'test/e2e/prerender-crawler.test.ts',
'test/e2e/prerender.test.ts',
'test/e2e/reload-scroll-backforward-restoration/index.test.ts',
'test/e2e/skip-trailing-slash-redirect/index.test.ts',
'test/e2e/streaming-ssr/index.test.ts',
'test/e2e/swc-warnings/index.test.ts',
'test/e2e/switchable-runtime/index.test.ts',
'test/examples/examples.test.ts',
'test/integration/404-page/test/index.test.js',
'test/integration/500-page/test/index.test.js',
'test/integration/amphtml-ssg/test/index.test.js',
'test/integration/amphtml/test/index.test.js',
'test/integration/api-support/test/index.test.js',
'test/integration/app-dir-export/test/dynamicapiroute-dev.test.ts',
'test/integration/app-dir-export/test/dynamicpage-dev.test.ts',
'test/integration/app-dir-export/test/trailing-slash-dev.test.ts',
'test/integration/app-document-add-hmr/test/index.test.js',
'test/integration/app-document-import-order/test/index.test.js',
'test/integration/app-document-remove-hmr/test/index.test.js',
'test/integration/app-document/test/index.test.js',
'test/integration/async-modules/test/index.test.js',
'test/integration/auto-export/test/index.test.js',
'test/integration/babel/test/index.test.js',
'test/integration/basepath-root-catch-all/test/index.test.js',
'test/integration/broken-webpack-plugin/test/index.test.js',
'test/integration/build-indicator/test/index.test.js',
'test/integration/build-spinners/index.test.ts',
'test/integration/cli/test/index.test.js',
'test/integration/client-navigation/test/index.test.js',
'test/integration/compression/test/index.test.js',
'test/integration/config-devtool-dev/test/index.test.js',
'test/integration/config-mjs/test/index.test.js',
'test/integration/config-output-export/test/index.test.ts',
'test/integration/config/test/index.test.js',
'test/integration/conflicting-app-page-error/test/index.test.js',
'test/integration/conflicting-public-file-page/test/index.test.js',
'test/integration/css/test/group-2.test.js',
'test/integration/custom-error/test/index.test.js',
'test/integration/custom-page-extension/test/index.test.js',
'test/integration/custom-routes-catchall/test/index.test.js',
'test/integration/custom-routes-i18n-index-redirect/test/index.test.js',
'test/integration/custom-routes-i18n/test/index.test.js',
'test/integration/custom-routes/test/index.test.js',
'test/integration/custom-server/test/index.test.js',
'test/integration/data-fetching-errors/test/index.test.js',
'test/integration/development-runtime-config/test/index.test.js',
'test/integration/draft-mode/test/index.test.ts',
'test/integration/dynamic-optional-routing-root-fallback/test/index.test.js',
'test/integration/dynamic-optional-routing-root-static-paths/test/index.test.js',
'test/integration/dynamic-optional-routing/test/index.test.js',
'test/integration/dynamic-routing/test/index.test.js',
'test/integration/dynamic-routing/test/middleware.test.js',
'test/integration/edge-runtime-configurable-guards/test/index.test.js',
'test/integration/edge-runtime-dynamic-code/test/index.test.js',
'test/integration/edge-runtime-module-errors/test/index.test.js',
'test/integration/edge-runtime-response-error/test/index.test.js',
'test/integration/edge-runtime-streaming-error/test/index.test.ts',
'test/integration/edge-runtime-with-node.js-apis/test/index.test.ts',
'test/integration/empty-project/test/index.test.js',
'test/integration/env-config/test/index.test.js',
'test/integration/fallback-false-rewrite/test/index.test.js',
'test/integration/fallback-route-params/test/index.test.js',
'test/integration/file-serving/test/index.test.js',
'test/integration/getinitialprops/test/index.test.js',
'test/integration/getserversideprops-preview/test/index.test.js',
'test/integration/gssp-redirect-base-path/test/index.test.js',
'test/integration/gssp-redirect-with-rewrites/test/index.test.js',
'test/integration/gssp-redirect/test/index.test.js',
'test/integration/i18n-support-base-path/test/index.test.js',
'test/integration/i18n-support-catchall/test/index.test.js',
'test/integration/i18n-support-custom-error/test/index.test.js',
'test/integration/i18n-support-fallback-rewrite-legacy/test/index.test.js',
'test/integration/i18n-support-fallback-rewrite/test/index.test.js',
'test/integration/i18n-support-index-rewrite/test/index.test.js',
'test/integration/i18n-support-same-page-hash-change/test/index.test.js',
'test/integration/i18n-support/test/index.test.js',
'test/integration/image-optimizer/test/content-disposition-type.test.ts',
'test/integration/image-optimizer/test/index.test.ts',
'test/integration/image-optimizer/test/minimum-cache-ttl.test.ts',
'test/integration/image-optimizer/test/sharp.test.ts',
'test/integration/image-optimizer/test/squoosh.test.ts',
'test/integration/import-assertion/test/index.test.js',
'test/integration/invalid-custom-routes/test/index.test.js',
'test/integration/invalid-middleware-matchers/test/index.test.js',
'test/integration/invalid-multi-match/test/index.test.js',
'test/integration/jsconfig-baseurl/test/index.test.js',
'test/integration/jsconfig-paths/test/index.test.js',
'test/integration/link-with-encoding/test/index.test.js',
'test/integration/middleware-dev-errors/test/index.test.js',
'test/integration/middleware-dev-update/test/index.test.js',
'test/integration/next-dynamic-lazy-compilation/test/index.test.js',
'test/integration/next-image-legacy/asset-prefix/test/index.test.ts',
'test/integration/next-image-legacy/base-path/test/index.test.ts',
'test/integration/next-image-legacy/base-path/test/static.test.ts',
'test/integration/next-image-legacy/default/test/index.test.ts',
'test/integration/next-image-legacy/image-from-node-modules/test/index.test.ts',
'test/integration/next-image-legacy/svgo-webpack/test/index.test.ts',
'test/integration/next-image-legacy/trailing-slash/test/index.test.ts',
'test/integration/next-image-legacy/typescript/test/index.test.ts',
'test/integration/next-image-legacy/unicode/test/index.test.ts',
'test/integration/next-image-new/app-dir/test/index.test.ts',
'test/integration/next-image-new/app-dir/test/static.test.ts',
'test/integration/next-image-new/asset-prefix/test/index.test.js',
'test/integration/next-image-new/base-path/test/index.test.js',
'test/integration/next-image-new/base-path/test/static.test.js',
'test/integration/next-image-new/both-basepath-trailingslash/test/index.test.ts',
'test/integration/next-image-new/default/test/index.test.ts',
'test/integration/next-image-new/default/test/static.test.ts',
'test/integration/next-image-new/export-config/test/index.test.ts',
'test/integration/next-image-new/image-from-node-modules/test/index.test.ts',
'test/integration/next-image-new/invalid-image-import/test/index.test.ts',
'test/integration/next-image-new/loader-config-edge-runtime/test/index.test.ts',
'test/integration/next-image-new/loader-config/test/index.test.ts',
'test/integration/next-image-new/svgo-webpack/test/index.test.ts',
'test/integration/next-image-new/typescript/test/index.test.ts',
'test/integration/next-image-new/unicode/test/index.test.ts',
'test/integration/no-duplicate-compile-error/test/index.test.js',
'test/integration/no-override-next-props/test/index.test.js',
'test/integration/node-fetch-keep-alive/test/index.test.js',
'test/integration/nullish-config/test/index.test.js',
'test/integration/prerender-fallback-encoding/test/index.test.js',
'test/integration/prerender-preview/test/index.test.js',
'test/integration/preview-fallback/test/index.test.js',
'test/integration/process-env-stub/test/index.test.js',
'test/integration/project-dir-delete/index.test.ts',
'test/integration/react-18/test/index.test.js',
'test/integration/relay-graphql-swc-multi-project/test/index.test.js',
'test/integration/repeated-slashes/test/index.test.js',
'test/integration/rewrite-with-browser-history/test/index.test.js',
'test/integration/rewrites-client-resolving/test/index.test.js',
'test/integration/rewrites-has-condition/test/index.test.js',
'test/integration/rewrites-manual-href-as/test/index.test.js',
'test/integration/route-index/test/index.test.js',
'test/integration/script-loader/test/index.test.js',
'test/integration/scroll-back-restoration/test/index.test.js',
'test/integration/scroll-forward-restoration/test/index.test.js',
'test/integration/server-asset-modules/test/index.test.js',
'test/integration/server-side-dev-errors/test/index.test.js',
'test/integration/telemetry/test/config.test.js',
'test/integration/telemetry/test/index.test.js',
'test/integration/telemetry/test/page-features.test.js',
'test/integration/trailing-slash-dist/test/index.test.js',
'test/integration/trailing-slashes-rewrite/test/index.test.js',
'test/integration/trailing-slashes/test/index.test.js',
'test/integration/turbopack-unsupported-log/index.test.ts',
'test/integration/typescript-app-type-declarations/test/index.test.js',
'test/integration/typescript-hmr/test/index.test.js',
'test/integration/typescript-only-remove-type-imports/test/index.test.js',
'test/integration/typescript-paths/test/index.test.js',
'test/integration/typescript-version-warning/test/index.test.js',
'test/integration/typescript-workspaces-paths/packages/www/test/index.test.js',
'test/integration/typescript/test/index.test.js',
'test/integration/undefined-webpack-config/test/index.test.js',
'test/integration/url-imports/test/index.test.js',
'test/integration/url/test/index.test.js',
'test/integration/with-router/test/index.test.js',
'test/integration/worker-webpack5/test/index.test.js',
'test/production/app-dir-edge-runtime-with-wasm/index.test.ts',
'test/production/app-dir-hide-suppressed-error-during-next-export/index.test.ts',
'test/production/app-dir-prefetch-non-iso-url/index.test.ts',
'test/production/app-dir/app-only-flag/app-only-flag.test.ts',
'test/production/app-dir/revalidate/revalidate.test.ts',
'test/production/ci-missing-typescript-deps/index.test.ts',
'test/production/custom-error-500/index.test.ts',
'test/production/custom-server/custom-server.test.ts',
'test/production/dependencies-can-use-env-vars-in-middlewares/index.test.ts',
'test/production/disable-fallback-polyfills/index.test.ts',
'test/production/edge-config-validations/index.test.ts',
'test/production/edge-runtime-is-addressable/index.test.ts',
'test/production/emit-decorator-metadata/index.test.ts',
'test/production/enoent-during-require/index.test.ts',
'test/production/escheck-output/index.test.ts',
'test/production/eslint-plugin-deps/index.test.ts',
'test/production/export/index.test.ts',
'test/production/exported-runtimes-value-validation/index.test.ts',
'test/production/fallback-export-error/index.test.ts',
'test/production/fatal-render-errror/index.test.ts',
'test/production/generate-middleware-source-maps/index.test.ts',
'test/production/jest/index.test.ts',
'test/production/jest/new-link-behavior.test.ts',
'test/production/jest/relay/relay-jest.test.ts',
'test/production/jest/remove-react-properties/remove-react-properties-jest.test.ts',
'test/production/jest/transpile-packages.test.ts',
'test/production/middleware-environment-variables-in-node-server-reflect-the-usage-inference/index.test.ts',
'test/production/middleware-typescript/test/index.test.ts',
'test/production/next-font/babel-unsupported.test.ts',
'test/production/next-font/telemetry-old.test.ts',
'test/production/next-font/telemetry.test.ts',
'test/production/pnpm-support/index.test.ts',
'test/production/postcss-plugin-config-as-string/index.test.ts',
'test/production/prerender-prefetch/index.test.ts',
'test/production/reading-request-body-in-middleware/index.test.ts',
'test/production/standalone-mode/metadata/index.test.ts',
'test/production/standalone-mode/optimizecss/index.test.ts',
'test/production/standalone-mode/required-server-files/required-server-files-app.test.ts',
'test/production/standalone-mode/required-server-files/required-server-files-i18n.test.ts',
'test/production/standalone-mode/required-server-files/required-server-files.test.ts',
'test/production/standalone-mode/response-cache/index.test.ts',
'test/production/standalone-mode/type-module/index.test.ts',
'test/production/supports-module-resolution-nodenext/supports-moduleresolution-nodenext.test.ts',
'test/production/typescript-basic/index.test.ts',
'test/unit/accept-headers.test.ts',
'test/unit/babel-plugin-next-page-config.test.ts',
'test/unit/babel-plugin-next-ssg-transform.test.ts',
'test/unit/cli.test.ts',
'test/unit/cssnano-simple/cssnano-preset-simple/issue-1.test.ts',
'test/unit/cssnano-simple/cssnano-preset-simple/plugin-config.test.ts',
'test/unit/cssnano-simple/cssnano-preset-simple/property-sorting.test.ts',
'test/unit/cssnano-simple/cssnano-simple/basic.test.ts',
'test/unit/cssnano-simple/cssnano-simple/exclude-all.test.ts',
'test/unit/cssnano-simple/cssnano-simple/plugin-config.test.ts',
'test/unit/eslint-plugin-next/google-font-display.test.ts',
'test/unit/eslint-plugin-next/google-font-preconnect.test.ts',
'test/unit/eslint-plugin-next/index.test.ts',
'test/unit/eslint-plugin-next/inline-script-id.test.ts',
'test/unit/eslint-plugin-next/next-script-for-ga.test.ts',
'test/unit/eslint-plugin-next/no-assign-module-variable.test.ts',
'test/unit/eslint-plugin-next/no-before-interactive-script-outside-document.test.ts',
'test/unit/eslint-plugin-next/no-css-tags.test.ts',
'test/unit/eslint-plugin-next/no-document-import-in-page.test.ts',
'test/unit/eslint-plugin-next/no-duplicate-head.test.ts',
'test/unit/eslint-plugin-next/no-head-element.test.ts',
'test/unit/eslint-plugin-next/no-head-import-in-document.test.ts',
'test/unit/eslint-plugin-next/no-html-link-for-pages.test.ts',
'test/unit/eslint-plugin-next/no-img-element.test.ts',
'test/unit/eslint-plugin-next/no-page-custom-font.test.ts',
'test/unit/eslint-plugin-next/no-script-component-in-head.test.ts',
'test/unit/eslint-plugin-next/no-styled-jsx-in-document.test.ts',
'test/unit/eslint-plugin-next/no-sync-scripts.test.ts',
'test/unit/eslint-plugin-next/no-title-in-document-head.test.ts',
'test/unit/eslint-plugin-next/no-typos.test.ts',
'test/unit/eslint-plugin-next/no-unwanted-polyfillio.test.ts',
'test/unit/esm-interpolate/esm-interpolate.test.tsx',
'test/unit/find-config.test.ts',
'test/unit/find-page-file.test.ts',
'test/unit/get-module-build-info.test.ts',
'test/unit/get-node-options-without-inspect.test.ts',
'test/unit/get-page-static-infos.test.ts',
'test/unit/get-project-dir.test.ts',
'test/unit/getDisplayName.test.ts',
'test/unit/htmlescape.test.ts',
'test/unit/image-optimizer/detect-content-type.test.ts',
'test/unit/image-optimizer/get-max-age.test.ts',
'test/unit/image-optimizer/match-remote-pattern.test.ts',
'test/unit/incremental-cache/file-system-cache.test.ts',
'test/unit/infer-get-server-side-props-type.test.ts',
'test/unit/infer-get-static-props.test.ts',
'test/unit/is-equal-node.unit.test.ts',
'test/unit/is-serializable-props.test.ts',
'test/unit/isolated/config.test.ts',
'test/unit/isolated/require-page.test.ts',
'test/unit/jest-next-swc.test.ts',
'test/unit/link-rendering.test.ts',
'test/unit/link-warnings.test.tsx',
'test/unit/loadGetInitialProps.test.ts',
'test/unit/mitt.test.ts',
'test/unit/next-babel-loader-dev.test.ts',
'test/unit/next-babel-loader-prod.test.ts',
'test/unit/next-babel.test.ts',
'test/unit/next-dynamic.test.tsx',
'test/unit/next-head-rendering.test.ts',
'test/unit/next-image-legacy.test.ts',
'test/unit/next-image-new.test.ts',
'test/unit/next-server-utils.test.ts',
'test/unit/next-swc.test.ts',
'test/unit/oxford-comma.test.ts',
'test/unit/page-route-sorter.test.ts',
'test/unit/parse-page-static-info.test.ts',
'test/unit/parse-relative-url.test.ts',
'test/unit/phaseConstants.test.ts',
'test/unit/preserve-process-env.test.ts',
'test/unit/recursive-copy.test.ts',
'test/unit/recursive-delete.test.ts',
'test/unit/recursive-readdir.test.ts',
'test/unit/router-add-base-path.test.ts',
'test/unit/split-cookies-string.test.ts',
'test/unit/validate-url.test.ts',
'test/unit/warn-removed-experimental-config.test.ts',
'test/unit/web-runtime/next-response-cookies.test.ts',
'test/unit/web-runtime/next-response.test.ts',
'test/unit/web-runtime/next-server-node.test.ts',
'test/unit/web-runtime/next-url.test.ts',
'test/unit/web-runtime/user-agent.test.ts',
'test/unit/webpack-config-overrides.test.ts',
'test/unit/write-app-declarations.test.ts',
'test/e2e/app-dir/search-params-react-key/layout-params.test.ts',
'test/e2e/app-dir/searchparams-static-bailout/searchparams-static-bailout.test.ts',
'test/e2e/app-dir/similar-pages-paths/similar-pages-paths.test.ts',
'test/e2e/app-dir/test-template/{{ toFileName name }}/{{ toFileName name }}.test.ts',
'test/e2e/app-dir/underscore-ignore-app-paths/underscore-ignore-app-paths.test.ts',
'test/e2e/app-dir/use-params/use-params.test.ts',
'test/e2e/app-dir/use-selected-layout-segment-s/use-selected-layout-segment-s.test.ts',
'test/e2e/browserslist-extends/index.test.ts',
'test/e2e/config-promise-export/async-function.test.ts',
'test/e2e/config-promise-export/promise.test.ts',
'test/e2e/disable-js-preload/test/index.test.js',
'test/e2e/dynamic-route-interpolation/index.test.ts',
'test/e2e/edge-api-endpoints-can-receive-body/index.test.ts',
'test/e2e/edge-async-local-storage/index.test.ts',
'test/e2e/edge-compiler-module-exports-preference/index.test.ts',
'test/e2e/edge-runtime-uses-edge-light-import-specifier-for-packages/edge-runtime-uses-edge-light-import-specifier-for-packages.test.ts',
'test/e2e/handle-non-hoisted-swc-helpers/index.test.ts',
'test/e2e/ignore-invalid-popstateevent/without-i18n.test.ts',
'test/e2e/link-with-api-rewrite/index.test.ts',
'test/e2e/middleware-base-path/test/index.test.ts',
'test/e2e/middleware-general/test/index.test.ts',
'test/e2e/middleware-responses/test/index.test.ts',
'test/e2e/middleware-shallow-link/index.test.ts',
'test/e2e/multi-zone/multi-zone.test.ts',
'test/e2e/new-link-behavior/child-a-tag-error.test.ts',
'test/e2e/new-link-behavior/index.test.ts',
'test/e2e/new-link-behavior/material-ui.test.ts',
'test/e2e/new-link-behavior/stitches.test.ts',
'test/e2e/new-link-behavior/typescript.test.ts',
'test/e2e/next-image-forward-ref/index.test.ts',
'test/e2e/no-eslint-warn-with-no-eslint-config/index.test.ts',
'test/e2e/nonce-head-manager/index.test.ts',
'test/e2e/optimized-loading/test/index.test.ts',
'test/e2e/postcss-config-cjs/index.test.ts',
'test/e2e/prerender-native-module.test.ts',
'test/e2e/proxy-request-with-middleware/test/index.test.ts',
'test/e2e/repeated-forward-slashes-error/repeated-forward-slashes-error.test.ts',
'test/e2e/ssr-react-context/index.test.ts',
'test/e2e/styled-jsx/index.test.ts',
'test/e2e/test-utils-tests/basic/basic.test.ts',
'test/e2e/trailingslash-with-rewrite/index.test.ts',
'test/e2e/transpile-packages/index.test.ts',
'test/e2e/type-module-interop/index.test.ts',
'test/e2e/undici-fetch/index.test.ts',
'test/e2e/yarn-pnp/test/with-eslint.test.ts',
'test/e2e/yarn-pnp/test/with-mdx.test.ts',
'test/e2e/yarn-pnp/test/with-next-sass.test.ts',
'test/integration/404-page-app/test/index.test.js',
'test/integration/404-page-custom-error/test/index.test.js',
'test/integration/404-page-ssg/test/index.test.js',
'test/integration/absolute-assetprefix/test/index.test.js',
'test/integration/amp-export-validation/test/index.test.js',
'test/integration/amphtml-custom-optimizer/test/index.test.js',
'test/integration/amphtml-custom-validator/test/index.test.js',
'test/integration/amphtml-fragment-style/test/index.test.js',
'test/integration/api-body-parser/test/index.test.js',
'test/integration/api-catch-all/test/index.test.js',
'test/integration/app-aspath/test/index.test.js',
'test/integration/app-dir-export/test/config.test.ts',
'test/integration/app-dir-export/test/dynamicapiroute-prod.test.ts',
'test/integration/app-dir-export/test/dynamicpage-prod.test.ts',
'test/integration/app-dir-export/test/start.test.ts',
'test/integration/app-dir-export/test/trailing-slash-start.test.ts',
'test/integration/app-document-style-fragment/test/index.test.js',
'test/integration/app-dynamic-error/test/index.test.ts',
'test/integration/app-functional/test/index.test.js',
'test/integration/app-tree/test/index.test.js',
'test/integration/app-types/app-types.test.js',
// TODO: should babel be tested with turbopack?
'test/integration/babel-custom/test/index.test.js',
'test/integration/bigint/test/index.test.js',
'test/integration/catches-missing-getStaticProps/test/index.test.js',
'test/integration/chunking/test/index.test.js',
'test/integration/client-404/test/index.test.js',
'test/integration/client-navigation-a11y/test/index.test.js',
'test/integration/client-shallow-routing/test/index.test.js',
'test/integration/config-experimental-warning/test/index.test.js',
'test/integration/config-promise-error/test/index.test.js',
'test/integration/config-resolve-alias/test/index.test.js',
'test/integration/css-customization/test/index.test.js',
'test/integration/css-features/test/index.test.js',
'test/integration/css-minify/test/index.test.js',
'test/integration/custom-error-page-exception/test/index.test.js',
'test/integration/dedupes-scripts/test/index.test.js',
'test/integration/development-hmr-refresh/test/index.test.js',
'test/integration/disable-js/test/index.test.js',
'test/integration/document-file-dependencies/test/index.test.js',
'test/integration/document-head-warnings/test/index.test.js',
'test/integration/duplicate-pages/test/index.test.js',
'test/integration/dynamic-require/test/index.test.js',
'test/integration/dynamic-route-rename/test/index.test.js',
'test/integration/empty-object-getInitialProps/test/index.test.js',
'test/integration/error-in-error/test/index.test.js',
'test/integration/error-load-fail/test/index.test.js',
'test/integration/error-plugin-stack-overflow/test/index.test.js',
'test/integration/errors-on-output-to-public/test/index.test.js',
'test/integration/errors-on-output-to-static/test/index.test.js',
'test/integration/eslint/test/index.test.js',
'test/integration/externalize-next-server/test/index.test.js',
'test/integration/externals-esm-loose/test/index.test.js',
'test/integration/externals-esm/test/index.test.js',
'test/integration/fallback-modules/test/index.test.js',
'test/integration/fetch-polyfill-ky-universal/test/index.test.js',
'test/integration/fetch-polyfill/test/index.test.js',
'test/integration/filesystempublicroutes/test/index.test.js',
'test/integration/firebase-grpc/test/index.test.js',
'test/integration/font-optimization/test/index.test.js',
'test/integration/future/test/index.test.js',
'test/integration/getserversideprops-export-error/test/index.test.js',
'test/integration/gip-identifier/test/index.test.js',
'test/integration/gsp-build-errors/test/index.test.js',
'test/integration/gsp-extension/test/index.test.js',
'test/integration/gssp-pageProps-merge/test/index.test.js',
'test/integration/handles-export-errors/test/index.test.js',
'test/integration/hashbang/test/index.test.js',
'test/integration/hydrate-then-render/test/index.test.js',
'test/integration/hydration/test/index.test.js',
'test/integration/image-generation/test/index.test.ts',
'test/integration/image-optimizer/test/old-sharp.test.ts',
'test/integration/index-index/test/index.test.js',
'test/integration/initial-ref/test/index.test.js',
'test/integration/invalid-config-values/test/index.test.js',
'test/integration/invalid-document-image-import/test/index.test.js',
'test/integration/invalid-href/test/index.test.js',
'test/integration/invalid-page-automatic-static-optimization/test/index.test.js',
'test/integration/invalid-revalidate-values/test/index.test.js',
'test/integration/invalid-server-options/test/index.test.js',
'test/integration/jsconfig-empty/test/index.test.js',
'test/integration/jsconfig/test/index.test.js',
'test/integration/json-serialize-original-error/test/index.test.js',
'test/integration/legacy-ssg-methods-error/test/index.test.js',
'test/integration/link-ref/test/index.test.js',
'test/integration/link-with-multiple-child/test/index.test.js',
'test/integration/link-without-router/test/index.test.js',
'test/integration/middleware-overrides-node.js-api/test/index.test.ts',
'test/integration/middleware-prefetch/tests/index.test.js',
'test/integration/middleware-src/test/index.test.js',
'test/integration/missing-document-component-error/test/index.test.js',
'test/integration/mixed-ssg-serverprops-error/test/index.test.js',
'test/integration/next-dynamic/test/index.test.js',
'test/integration/next-image-legacy/basic/test/index.test.ts',
'test/integration/next-image-legacy/custom-resolver/test/index.test.ts',
'test/integration/next-image-legacy/default/test/static.test.ts',
'test/integration/next-image-legacy/no-intersection-observer-fallback/test/index.test.ts',
'test/integration/next-image-legacy/noscript/test/index.test.ts',
'test/integration/next-image-legacy/react-virtualized/test/index.test.ts',
'test/integration/next-image-new/react-virtualized/test/index.test.ts',
'test/integration/no-op-export/test/index.test.js',
'test/integration/no-page-props/test/index.test.js',
'test/integration/non-next-dist-exclude/test/index.test.js',
'test/integration/non-standard-node-env-warning/test/index.test.js',
'test/integration/not-found-revalidate/test/index.test.js',
'test/integration/numeric-sep/test/index.test.js',
'test/integration/ondemand/test/index.test.js',
'test/integration/optional-chaining-nullish-coalescing/test/index.test.js',
'test/integration/page-config/test/index.test.js',
'test/integration/page-extensions/test/index.test.js',
'test/integration/plugin-mdx-rs/test/index.test.js',
'test/integration/polyfilling-minimal/test/index.test.js',
'test/integration/polyfills/test/index.test.js',
'test/integration/port-env-var/test/index.test.js',
'test/integration/preload-viewport/test/index.test.js',
'test/integration/prerender-invalid-catchall-params/test/index.test.js',
'test/integration/prerender-invalid-paths/test/index.test.js',
'test/integration/prerender-legacy/test/index.test.js',
'test/integration/prerender-no-revalidate/test/index.test.js',
'test/integration/prerender-revalidate/test/index.test.js',
'test/integration/production-browser-sourcemaps/test/index.test.js',
'test/integration/production-build-dir/test/index.test.js',
'test/integration/production-config/test/index.test.js',
'test/integration/production-nav/test/index.test.js',
'test/integration/production-start-no-build/test/index.test.js',
'test/integration/production/test/index.test.js',
'test/integration/query-with-encoding/test/index.test.js',
'test/integration/re-export-all-exports-from-page-disallowed/test/index.test.js',
'test/integration/react-profiling-mode/test/index.test.js',
'test/integration/react-streaming/test/index.test.js',
'test/integration/read-only-source-hmr/test/index.test.js',
'test/integration/relay-analytics-disabled/test/index.test.js',
'test/integration/relay-analytics/test/index.test.js',
'test/integration/render-error-on-module-error/test/index.test.js',
'test/integration/render-error-on-top-level-error/with-get-initial-props/test/index.test.js',
'test/integration/render-error-on-top-level-error/without-get-initial-props/test/index.test.js',
'test/integration/required-server-files-ssr-404/test/index.test.js',
'test/integration/revalidate-as-path/test/index.test.js',
'test/integration/rewrites-destination-query-array/test/index.test.js',
'test/integration/root-optional-revalidate/test/index.test.js',
'test/integration/route-indexes/test/index.test.js',
'test/integration/route-load-cancel-css/test/index.test.js',
'test/integration/route-load-cancel/test/index.test.js',
'test/integration/router-hash-navigation/test/index.test.js',
'test/integration/router-is-ready-app-gip/test/index.test.js',
'test/integration/router-is-ready/test/index.test.js',
'test/integration/router-prefetch/test/index.test.js',
'test/integration/scss/test/group-2.test.js',
'test/integration/src-dir-support-double-dir/test/index.test.js',
'test/integration/src-dir-support/test/index.test.js',
'test/integration/ssg-data-404/test/index.test.js',
'test/integration/ssg-dynamic-routes-404-page/test/index.test.js',
'test/integration/static-404/test/index.test.js',
'test/integration/static-page-name/test/index.test.js',
'test/integration/styled-jsx-plugin/test/index.test.js',
'test/integration/tsconfig-verifier/test/index.test.js',
'test/integration/turbotrace-with-webpack-worker/test/index.test.js',
'test/integration/typeof-window-replace/test/index.test.js',
'test/integration/typescript-baseurl/test/index.test.js',
'test/integration/typescript-custom-tsconfig/test/index.test.js',
'test/integration/typescript-filtered-files/test/index.test.js',
'test/integration/typescript-ignore-errors/test/index.test.js',
'test/integration/webpack-config-extensionalias/test/index.test.js',
'test/integration/webpack-config-mainjs/test/index.test.js',
]
module.exports = {
disabledTests,
}
module.exports = { enabledTests }

View file

@ -16,7 +16,7 @@ const exec = promisify(execOrig)
// If process.argv contains a test to be executed, this'll append it to the list.
const externalTestsFilterLists = process.env.NEXT_EXTERNAL_TESTS_FILTERS
? require(process.env.NEXT_EXTERNAL_TESTS_FILTERS)
: { enabledTests: [], disabledTests: [] }
: { enabledTests: [] }
const timings = []
const DEFAULT_NUM_RETRIES = os.platform() === 'win32' ? 2 : 1
const DEFAULT_CONCURRENCY = 2
@ -201,12 +201,10 @@ async function main() {
}
// If there are external manifest contains list of tests, apply it to the test lists.
// Specifically, we filters out `disabledTests` from named export of the manifest.
if (externalTestsFilterLists?.disabledTests.length > 0) {
tests = tests.filter(
(test) =>
!externalTestsFilterLists.disabledTests.some((disabled) =>
disabled.includes(test)
if (externalTestsFilterLists?.enabledTests.length > 0) {
tests = tests.filter((test) =>
externalTestsFilterLists.enabledTests.some((enabled) =>
enabled.includes(test)
)
)
}

View file

@ -5,7 +5,8 @@ import { NextInstance } from 'test/lib/next-modes/base'
import { createNext } from 'e2e-utils'
import stripAnsi from 'strip-ansi'
describe('Project Directory Renaming', () => {
// TODO: investigate occasional failure
describe.skip('Project Directory Renaming', () => {
let next: NextInstance
beforeAll(async () => {

View file

@ -407,7 +407,8 @@ createNextDescribe(
}, 'success')
})
it('should handle revalidateTag', async () => {
// TODO: investigate flakiness when deployed
it.skip('should handle revalidateTag', async () => {
const browser = await next.browser('/revalidate')
const randomNumber = await browser.elementByCss('#random-number').text()
const justPutIt = await browser.elementByCss('#justputit').text()
@ -415,8 +416,6 @@ createNextDescribe(
await browser.elementByCss('#revalidate-justputit').click()
// TODO: investigate flakiness when deployed
if (!isNextDeploy) {
await check(async () => {
const newRandomNumber = await browser
.elementByCss('#random-number')
@ -432,7 +431,6 @@ createNextDescribe(
return 'success'
}, 'success')
}
})
it('should handle revalidateTag + redirect', async () => {

View file

@ -0,0 +1,238 @@
/* eslint-env jest */
import fs from 'fs-extra'
import { join } from 'path'
import {
killApp,
findPort,
launchApp,
nextStart,
nextBuild,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
const appDir = join(__dirname, '../')
const pages500 = join(appDir, 'pages/500.js')
const pagesApp = join(appDir, 'pages/_app.js')
const pagesError = join(appDir, 'pages/_error.js')
const gip500Err =
/`pages\/500` can not have getInitialProps\/getServerSideProps/
let appPort
let app
it('does not show error with getStaticProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(code).toBe(0)
})
it('does not show error with getStaticProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/abc')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
})
it('shows error with getServerSideProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
expect(code).toBe(1)
})
it('shows error with getServerSideProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/500')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
})
it('does build 500 statically with getInitialProps in _app and getStaticProps in pages/500', async () => {
await fs.writeFile(
pagesApp,
`
import App from 'next/app'
const page = ({ Component, pageProps }) => <Component {...pageProps} />
page.getInitialProps = (ctx) => App.getInitialProps(ctx)
export default page
`
)
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => {
console.log('rendered 500')
return 'custom 500 page'
}
export default page
export const getStaticProps = () => {
return {
props: {}
}
}
`
)
await fs.remove(join(appDir, '.next'))
const {
stderr,
stdout: buildStdout,
code,
} = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
await fs.remove(pagesApp)
await fs.remove(pages500)
await fs.rename(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(buildStdout).toContain('rendered 500')
expect(code).toBe(0)
expect(await fs.pathExists(join(appDir, '.next/server/pages/500.html'))).toBe(
true
)
let appStdout = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout(msg) {
appStdout += msg || ''
},
onStderr(msg) {
appStdout += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStdout).not.toContain('rendered 500')
})
it('does not build 500 statically with no pages/500 and getServerSideProps in _error', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
export const getServerSideProps = ({ req, res, err }) => {
console.error('called _error getServerSideProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
props: {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(await fs.pathExists(join(appDir, '.next/server/pages/500.html'))).toBe(
false
)
let appStderr = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStderr(msg) {
appStderr += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStderr).toContain('called _error getServerSideProps')
})

View file

@ -130,72 +130,6 @@ describe('500 Page Support', () => {
expect(appStdout).toContain('rendered 500')
})
it('does build 500 statically with getInitialProps in _app and getStaticProps in pages/500', async () => {
await fs.writeFile(
pagesApp,
`
import App from 'next/app'
const page = ({ Component, pageProps }) => <Component {...pageProps} />
page.getInitialProps = (ctx) => App.getInitialProps(ctx)
export default page
`
)
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => {
console.log('rendered 500')
return 'custom 500 page'
}
export default page
export const getStaticProps = () => {
return {
props: {}
}
}
`
)
await fs.remove(join(appDir, '.next'))
const {
stderr,
stdout: buildStdout,
code,
} = await nextBuild(appDir, [], {
stderr: true,
stdout: true,
})
await fs.remove(pagesApp)
await fs.remove(pages500)
await fs.rename(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(buildStdout).toContain('rendered 500')
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(true)
let appStdout = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout(msg) {
appStdout += msg || ''
},
onStderr(msg) {
appStdout += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStdout).not.toContain('rendered 500')
})
it('builds 500 statically by default with no pages/500', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.remove(join(appDir, '.next'))
@ -311,59 +245,6 @@ describe('500 Page Support', () => {
expect(appStderr).toContain('called _error.getInitialProps')
})
it('does not build 500 statically with no pages/500 and getServerSideProps in _error', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
pagesError,
`
function Error({ statusCode }) {
return <p>Error status: {statusCode}</p>
}
export const getServerSideProps = ({ req, res, err }) => {
console.error('called _error getServerSideProps')
if (req.url === '/500') {
throw new Error('should not export /500')
}
return {
props: {
statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404
}
}
}
export default Error
`
)
await fs.remove(join(appDir, '.next'))
const { stderr: buildStderr, code } = await nextBuild(appDir, [], {
stderr: true,
})
await fs.rename(`${pages500}.bak`, pages500)
await fs.remove(pagesError)
console.log(buildStderr)
expect(buildStderr).not.toMatch(gip500Err)
expect(code).toBe(0)
expect(
await fs.pathExists(join(appDir, '.next/server/pages/500.html'))
).toBe(false)
let appStderr = ''
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStderr(msg) {
appStderr += msg || ''
},
})
await renderViaHTTP(appPort, '/err')
await killApp(app)
expect(appStderr).toContain('called _error getServerSideProps')
})
it('does not build 500 statically with no pages/500 and custom getInitialProps in _error and _app', async () => {
await fs.rename(pages500, `${pages500}.bak`)
await fs.writeFile(
@ -471,100 +352,4 @@ describe('500 Page Support', () => {
expect(stderr).toMatch(gip500Err)
})
it('does not show error with getStaticProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
expect(code).toBe(0)
})
it('does not show error with getStaticProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getStaticProps = () => ({ props: { a: 'b' } })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/abc')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).not.toMatch(gip500Err)
})
it('shows error with getServerSideProps in pages/500 build', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
await fs.remove(join(appDir, '.next'))
const { stderr, code } = await nextBuild(appDir, [], { stderr: true })
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
expect(code).toBe(1)
})
it('shows error with getServerSideProps in pages/500 dev', async () => {
await fs.move(pages500, `${pages500}.bak`)
await fs.writeFile(
pages500,
`
const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
export default page
`
)
let stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
stderr += msg || ''
},
})
await renderViaHTTP(appPort, '/500')
await waitFor(1000)
await killApp(app)
await fs.remove(pages500)
await fs.move(`${pages500}.bak`, pages500)
expect(stderr).toMatch(gip500Err)
})
})

View file

@ -475,416 +475,4 @@ describe('create next app', () => {
shouldBeJavascriptProject({ cwd, projectName, template: 'app' })
})
})
it('should use npm as the package manager on supplying --use-npm', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-npm',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
}
)
expect(res.exitCode).toBe(0)
shouldBeJavascriptProject({ cwd, projectName, template: 'app' })
})
})
it('should use npm as the package manager on supplying --use-npm with example', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-npm',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd }
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'pages/index.tsx',
'.gitignore',
'package-lock.json',
'node_modules/next',
],
})
})
})
it('should use Yarn as the package manager on supplying --use-yarn', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-yarn'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-yarn',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
}
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'yarn.lock',
'node_modules/next',
],
})
})
})
it('should use Yarn as the package manager on supplying --use-yarn with example', async () => {
try {
await execa('yarn', ['--version'])
} catch (_) {
// install yarn if not available
await execa('npm', ['i', '-g', 'yarn'])
}
await useTempDir(async (cwd) => {
const projectName = 'use-yarn'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-yarn',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd }
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'pages/index.tsx',
'.gitignore',
'yarn.lock',
'node_modules/next',
],
})
})
})
it('should use pnpm as the package manager on supplying --use-pnpm', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-pnpm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-pnpm',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
}
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'pnpm-lock.yaml',
'node_modules/next',
],
})
})
})
it('should use pnpm as the package manager on supplying --use-pnpm with example', async () => {
try {
await execa('pnpm', ['--version'])
} catch (_) {
// install pnpm if not available
await execa('npm', ['i', '-g', 'pnpm'])
}
await useTempDir(async (cwd) => {
const projectName = 'use-pnpm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-pnpm',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd }
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'pages/index.tsx',
'.gitignore',
'pnpm-lock.yaml',
'node_modules/next',
],
})
})
})
it('should infer npm as the package manager', async () => {
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
env: { ...process.env, npm_config_user_agent: 'npm' },
}
)
const files = [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'package-lock.json',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer npm as the package manager with example', async () => {
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd, env: { ...process.env, npm_config_user_agent: 'npm' } }
)
const files = [
'package.json',
'pages/index.tsx',
'.gitignore',
'package-lock.json',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer yarn as the package manager', async () => {
try {
await execa('yarn', ['--version'])
} catch (_) {
// install yarn if not available
await execa('npm', ['i', '-g', 'yarn'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-yarn'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
env: { ...process.env, npm_config_user_agent: 'yarn' },
}
)
const files = [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'yarn.lock',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer yarn as the package manager with example', async () => {
try {
await execa('yarn', ['--version'])
} catch (_) {
// install yarn if not available
await execa('npm', ['i', '-g', 'yarn'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd, env: { ...process.env, npm_config_user_agent: 'yarn' } }
)
const files = [
'package.json',
'pages/index.tsx',
'.gitignore',
'yarn.lock',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer pnpm as the package manager', async () => {
try {
await execa('pnpm', ['--version'])
} catch (_) {
// install pnpm if not available
await execa('npm', ['i', '-g', 'pnpm'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
env: { ...process.env, npm_config_user_agent: 'pnpm' },
}
)
const files = [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'pnpm-lock.yaml',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer pnpm as the package manager with example', async () => {
try {
await execa('pnpm', ['--version'])
} catch (_) {
// install pnpm if not available
await execa('npm', ['i', '-g', 'pnpm'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd, env: { ...process.env, npm_config_user_agent: 'pnpm' } }
)
const files = [
'package.json',
'pages/index.tsx',
'.gitignore',
'pnpm-lock.yaml',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
})

View file

@ -0,0 +1,438 @@
/* eslint-env jest */
/**
* @fileoverview
*
* This file contains integration tests for `create-next-app`. It currently
* aliases all calls to `--js`.
*
* TypeScript project creation via `create-next-app --ts` is tested in
* `./templates.test.ts`, though additional tests can be added here using the
* `shouldBeTypescriptProject` helper.
*/
import execa from 'execa'
import Conf from 'next/dist/compiled/conf'
import { useTempDir } from '../../lib/use-temp-dir'
import { projectFilesShouldExist, shouldBeJavascriptProject } from './lib/utils'
const cli = require.resolve('create-next-app/dist/index.js')
const exampleRepo = 'https://github.com/vercel/next.js/tree/canary'
const examplePath = 'examples/basic-css'
const run = (args: string[], options: execa.Options) => {
const conf = new Conf({ projectName: 'create-next-app' })
conf.clear()
return execa('node', [cli].concat(args), options)
}
it('should use npm as the package manager on supplying --use-npm', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-npm',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
}
)
expect(res.exitCode).toBe(0)
shouldBeJavascriptProject({ cwd, projectName, template: 'app' })
})
})
it('should use npm as the package manager on supplying --use-npm with example', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-npm',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd }
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'pages/index.tsx',
'.gitignore',
'package-lock.json',
'node_modules/next',
],
})
})
})
it('should use Yarn as the package manager on supplying --use-yarn', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-yarn'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-yarn',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
}
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'yarn.lock',
'node_modules/next',
],
})
})
})
it('should use Yarn as the package manager on supplying --use-yarn with example', async () => {
try {
await execa('yarn', ['--version'])
} catch (_) {
// install yarn if not available
await execa('npm', ['i', '-g', 'yarn'])
}
await useTempDir(async (cwd) => {
const projectName = 'use-yarn'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-yarn',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd }
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'pages/index.tsx',
'.gitignore',
'yarn.lock',
'node_modules/next',
],
})
})
})
it('should use pnpm as the package manager on supplying --use-pnpm', async () => {
await useTempDir(async (cwd) => {
const projectName = 'use-pnpm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-pnpm',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
}
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'pnpm-lock.yaml',
'node_modules/next',
],
})
})
})
it('should use pnpm as the package manager on supplying --use-pnpm with example', async () => {
try {
await execa('pnpm', ['--version'])
} catch (_) {
// install pnpm if not available
await execa('npm', ['i', '-g', 'pnpm'])
}
await useTempDir(async (cwd) => {
const projectName = 'use-pnpm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--use-pnpm',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd }
)
expect(res.exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'package.json',
'pages/index.tsx',
'.gitignore',
'pnpm-lock.yaml',
'node_modules/next',
],
})
})
})
it('should infer npm as the package manager', async () => {
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
env: { ...process.env, npm_config_user_agent: 'npm' },
}
)
const files = [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'package-lock.json',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer npm as the package manager with example', async () => {
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd, env: { ...process.env, npm_config_user_agent: 'npm' } }
)
const files = [
'package.json',
'pages/index.tsx',
'.gitignore',
'package-lock.json',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer yarn as the package manager', async () => {
try {
await execa('yarn', ['--version'])
} catch (_) {
// install yarn if not available
await execa('npm', ['i', '-g', 'yarn'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-yarn'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
env: { ...process.env, npm_config_user_agent: 'yarn' },
}
)
const files = [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'yarn.lock',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer yarn as the package manager with example', async () => {
try {
await execa('yarn', ['--version'])
} catch (_) {
// install yarn if not available
await execa('npm', ['i', '-g', 'yarn'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd, env: { ...process.env, npm_config_user_agent: 'yarn' } }
)
const files = [
'package.json',
'pages/index.tsx',
'.gitignore',
'yarn.lock',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer pnpm as the package manager', async () => {
try {
await execa('pnpm', ['--version'])
} catch (_) {
// install pnpm if not available
await execa('npm', ['i', '-g', 'pnpm'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--no-src-dir',
'--app',
`--import-alias=@/*`,
],
{
cwd,
env: { ...process.env, npm_config_user_agent: 'pnpm' },
}
)
const files = [
'package.json',
'app/page.js',
'.gitignore',
'.eslintrc.json',
'pnpm-lock.yaml',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})
it('should infer pnpm as the package manager with example', async () => {
try {
await execa('pnpm', ['--version'])
} catch (_) {
// install pnpm if not available
await execa('npm', ['i', '-g', 'pnpm'])
}
await useTempDir(async (cwd) => {
const projectName = 'infer-package-manager-npm'
const res = await run(
[
projectName,
'--js',
'--no-tailwind',
'--eslint',
'--example',
`${exampleRepo}/${examplePath}`,
],
{ cwd, env: { ...process.env, npm_config_user_agent: 'pnpm' } }
)
const files = [
'package.json',
'pages/index.tsx',
'.gitignore',
'pnpm-lock.yaml',
'node_modules/next',
]
expect(res.exitCode).toBe(0)
projectFilesShouldExist({ cwd, projectName, files })
})
})

View file

@ -0,0 +1,196 @@
/* eslint-env jest */
/**
* @fileoverview
*
* This file contains tests for `create-next-app` templates, currently
* JavaScript (default), TypeScript, and appDir.
*/
import path from 'path'
import fs from 'fs-extra'
import {
createNextApp,
shouldBeTemplateProject,
spawnExitPromise,
} from './lib/utils'
import { Span } from 'next/dist/trace'
import { useTempDir } from '../../lib/use-temp-dir'
import { fetchViaHTTP, findPort, killApp, launchApp } from 'next-test-utils'
import resolveFrom from 'resolve-from'
import { createNextInstall } from '../../lib/create-next-install'
const startsWithoutError = async (
appDir: string,
modes = ['default', 'turbo'],
usingAppDirectory: boolean = false
) => {
for (const mode of modes) {
appDir = await fs.realpath(appDir)
const appPort = await findPort()
const app = await launchApp(appDir, appPort, {
turbo: mode === 'turbo',
cwd: appDir,
nextBin: resolveFrom(appDir, 'next/dist/bin/next'),
})
try {
const res = await fetchViaHTTP(appPort, '/')
expect(await res.text()).toContain('Get started by editing')
expect(res.status).toBe(200)
if (!usingAppDirectory) {
const apiRes = await fetchViaHTTP(appPort, '/api/hello')
expect(await apiRes.json()).toEqual({ name: 'John Doe' })
expect(apiRes.status).toBe(200)
}
} finally {
await killApp(app)
}
}
}
let testVersion
describe('create-next-app --app', () => {
beforeAll(async () => {
if (testVersion) return
const span = new Span({ name: 'parent' })
testVersion = (
await createNextInstall({ onlyPackages: true, parentSpan: span })
).get('next')
})
it('should create TS appDir projects with --ts', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-test'
const childProcess = createNextApp(
[
projectName,
'--ts',
'--no-tailwind',
'--app',
'--eslint',
'--no-src-dir',
`--import-alias=@/*`,
],
{
cwd,
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({ cwd, projectName, template: 'app', mode: 'ts' })
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
it('should create JS appDir projects with --js', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-test'
const childProcess = createNextApp(
[
projectName,
'--js',
'--no-tailwind',
'--app',
'--eslint',
'--no-src-dir',
`--import-alias=@/*`,
],
{
cwd,
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({ cwd, projectName, template: 'app', mode: 'js' })
// is landed
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
it('should create JS appDir projects with --js --src-dir', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-test'
const childProcess = createNextApp(
[
projectName,
'--js',
'--no-tailwind',
'--app',
'--eslint',
'--src-dir',
'--import-alias=@/*',
],
{
cwd,
stdio: 'inherit',
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({
cwd,
projectName,
template: 'app',
mode: 'js',
srcDir: true,
})
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
it('should create Tailwind CSS appDir projects with --tailwind', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-tailwind-test'
const childProcess = createNextApp(
[
projectName,
'--ts',
'--tailwind',
'--app',
'--eslint',
'--src-dir',
`--import-alias=@/*`,
],
{
cwd,
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({
cwd,
projectName,
template: 'app-tw',
mode: 'ts',
srcDir: true,
})
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
})

View file

@ -12,13 +12,12 @@ import {
createNextApp,
projectFilesShouldExist,
shouldBeJavascriptProject,
shouldBeTemplateProject,
shouldBeTypescriptProject,
spawnExitPromise,
} from './lib/utils'
import { Span } from 'next/dist/trace'
import { useTempDir } from '../../../test/lib/use-temp-dir'
import { useTempDir } from '../../lib/use-temp-dir'
import {
check,
fetchViaHTTP,
@ -27,7 +26,7 @@ import {
launchApp,
} from 'next-test-utils'
import resolveFrom from 'resolve-from'
import { createNextInstall } from '../../../test/lib/create-next-install'
import { createNextInstall } from '../../lib/create-next-install'
import ansiEscapes from 'ansi-escapes'
const startsWithoutError = async (
@ -176,29 +175,6 @@ describe('create-next-app templates', () => {
})
})
it('should create TS projects with --ts, --typescript with CI=1', async () => {
await useTempDir(async (cwd) => {
const projectName = 'typescript-test'
const childProcess = createNextApp(
[projectName, '--ts', '--no-tailwind', '--eslint', '--app'],
{
cwd,
env: {
...process.env,
CI: '1',
GITHUB_ACTIONS: '1',
},
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTypescriptProject({ cwd, projectName, template: 'app' })
await startsWithoutError(path.join(cwd, projectName), undefined, true)
})
})
it('should create JS projects with --js, --javascript', async () => {
await useTempDir(async (cwd) => {
const projectName = 'javascript-test'
@ -414,147 +390,3 @@ describe('create-next-app templates', () => {
})
})
})
describe('create-next-app --app', () => {
beforeAll(async () => {
if (testVersion) return
const span = new Span({ name: 'parent' })
testVersion = (
await createNextInstall({ onlyPackages: true, parentSpan: span })
).get('next')
})
it('should create TS appDir projects with --ts', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-test'
const childProcess = createNextApp(
[
projectName,
'--ts',
'--no-tailwind',
'--app',
'--eslint',
'--no-src-dir',
`--import-alias=@/*`,
],
{
cwd,
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({ cwd, projectName, template: 'app', mode: 'ts' })
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
it('should create JS appDir projects with --js', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-test'
const childProcess = createNextApp(
[
projectName,
'--js',
'--no-tailwind',
'--app',
'--eslint',
'--no-src-dir',
`--import-alias=@/*`,
],
{
cwd,
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({ cwd, projectName, template: 'app', mode: 'js' })
// is landed
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
it('should create JS appDir projects with --js --src-dir', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-test'
const childProcess = createNextApp(
[
projectName,
'--js',
'--no-tailwind',
'--app',
'--eslint',
'--src-dir',
'--import-alias=@/*',
],
{
cwd,
stdio: 'inherit',
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({
cwd,
projectName,
template: 'app',
mode: 'js',
srcDir: true,
})
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
it('should create Tailwind CSS appDir projects with --tailwind', async () => {
await useTempDir(async (cwd) => {
const projectName = 'appdir-tailwind-test'
const childProcess = createNextApp(
[
projectName,
'--ts',
'--tailwind',
'--app',
'--eslint',
'--src-dir',
`--import-alias=@/*`,
],
{
cwd,
},
testVersion
)
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)
shouldBeTemplateProject({
cwd,
projectName,
template: 'app-tw',
mode: 'ts',
srcDir: true,
})
await startsWithoutError(
path.join(cwd, projectName),
['default', 'turbo'],
true
)
})
})
})

View file

@ -0,0 +1,71 @@
/* eslint-env jest */
import { readdir, readFile, remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../fixtures')
describe('Browserslist: Old', () => {
const appDir = join(fixturesDir, 'browsers-old')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"a{-webkit-animation:none 0s ease 0s 1 normal none running;animation:none 0s ease 0s 1 normal none running;-webkit-backface-visibility:visible;backface-visibility:visible;background:transparent none repeat 0 0/auto auto padding-box border-box scroll;border:none;border-collapse:separate;-webkit-border-image:none;border-image:none;-webkit-border-radius:0;border-radius:0;border-spacing:0;bottom:auto;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;caption-side:top;clear:none;clip:auto;color:#000;-webkit-columns:auto;-webkit-column-count:auto;-webkit-column-fill:balance;column-fill:balance;-webkit-column-gap:normal;column-gap:normal;-webkit-column-rule:medium none currentColor;column-rule:medium none currentColor;-webkit-column-span:1;column-span:1;-webkit-column-width:auto;columns:auto;content:normal;counter-increment:none;counter-reset:none;cursor:auto;direction:ltr;display:inline;empty-cells:show;float:none;font-family:serif;font-size:medium;font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;line-height:normal;height:auto;-ms-hyphens:none;hyphens:none;left:auto;letter-spacing:normal;list-style:disc none outside;margin:0;max-height:none;max-width:none;min-height:0;min-width:0;opacity:1;orphans:2;outline:medium none invert;overflow:visible;overflow-x:visible;overflow-y:visible;padding:0;page-break-after:auto;page-break-before:auto;page-break-inside:auto;-webkit-perspective:none;perspective:none;-webkit-perspective-origin:50% 50%;perspective-origin:50% 50%;position:static;right:auto;tab-size:8;table-layout:auto;text-align:left;text-align-last:auto;text-decoration:none;text-indent:0;text-shadow:none;text-transform:none;top:auto;-webkit-transform:none;transform:none;-webkit-transform-origin:50% 50% 0;transform-origin:50% 50% 0;-webkit-transform-style:flat;transform-style:flat;-webkit-transition:none 0s ease 0s;transition:none 0s ease 0s;unicode-bidi:normal;vertical-align:baseline;visibility:visible;white-space:normal;widows:2;width:auto;word-spacing:normal;z-index:auto;all:initial}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:2dppx){.image{background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}}"`
)
})
})
describe('Browserslist: New', () => {
const appDir = join(fixturesDir, 'browsers-new')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"a{all:initial}@media (min-resolution:2dppx){.image{background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}}"`
)
})
})

View file

@ -0,0 +1,125 @@
/* eslint-env jest */
import { readdir, readFile, remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../fixtures')
describe('Custom Properties: Fail for :root {} in CSS Modules', () => {
const appDir = join(fixturesDir, 'cp-global-modules')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('pages/styles.module.css')
expect(stderr).toContain('Selector ":root" is not pure')
})
})
describe('Custom Properties: Fail for global element in CSS Modules', () => {
const appDir = join(fixturesDir, 'cp-el-modules')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('pages/styles.module.css')
expect(stderr).toContain('Selector "h1" is not pure')
})
})
describe('CSS Modules: Import Global CSS', () => {
const appDir = join(fixturesDir, 'module-import-global')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"a .styles_foo__Io_Us{all:initial}"`
)
})
})
describe('CSS Modules: Importing Invalid Global CSS', () => {
const appDir = join(fixturesDir, 'module-import-global-invalid')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('pages/styles.css')
expect(stderr).toContain('Selector "a" is not pure')
})
})
describe('CSS Modules: Import Exports', () => {
const appDir = join(fixturesDir, 'module-import-exports')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".styles_blk__CqbFg{color:#000}"`
)
})
})

View file

@ -6,70 +6,6 @@ import { join } from 'path'
const fixturesDir = join(__dirname, '../fixtures')
describe('Browserslist: Old', () => {
const appDir = join(fixturesDir, 'browsers-old')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"a{-webkit-animation:none 0s ease 0s 1 normal none running;animation:none 0s ease 0s 1 normal none running;-webkit-backface-visibility:visible;backface-visibility:visible;background:transparent none repeat 0 0/auto auto padding-box border-box scroll;border:none;border-collapse:separate;-webkit-border-image:none;border-image:none;-webkit-border-radius:0;border-radius:0;border-spacing:0;bottom:auto;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;caption-side:top;clear:none;clip:auto;color:#000;-webkit-columns:auto;-webkit-column-count:auto;-webkit-column-fill:balance;column-fill:balance;-webkit-column-gap:normal;column-gap:normal;-webkit-column-rule:medium none currentColor;column-rule:medium none currentColor;-webkit-column-span:1;column-span:1;-webkit-column-width:auto;columns:auto;content:normal;counter-increment:none;counter-reset:none;cursor:auto;direction:ltr;display:inline;empty-cells:show;float:none;font-family:serif;font-size:medium;font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;line-height:normal;height:auto;-ms-hyphens:none;hyphens:none;left:auto;letter-spacing:normal;list-style:disc none outside;margin:0;max-height:none;max-width:none;min-height:0;min-width:0;opacity:1;orphans:2;outline:medium none invert;overflow:visible;overflow-x:visible;overflow-y:visible;padding:0;page-break-after:auto;page-break-before:auto;page-break-inside:auto;-webkit-perspective:none;perspective:none;-webkit-perspective-origin:50% 50%;perspective-origin:50% 50%;position:static;right:auto;tab-size:8;table-layout:auto;text-align:left;text-align-last:auto;text-decoration:none;text-indent:0;text-shadow:none;text-transform:none;top:auto;-webkit-transform:none;transform:none;-webkit-transform-origin:50% 50% 0;transform-origin:50% 50% 0;-webkit-transform-style:flat;transform-style:flat;-webkit-transition:none 0s ease 0s;transition:none 0s ease 0s;unicode-bidi:normal;vertical-align:baseline;visibility:visible;white-space:normal;widows:2;width:auto;word-spacing:normal;z-index:auto;all:initial}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:2dppx){.image{background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}}"`
)
})
})
describe('Browserslist: New', () => {
const appDir = join(fixturesDir, 'browsers-new')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"a{all:initial}@media (min-resolution:2dppx){.image{background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}}"`
)
})
})
describe('Custom Properties: Pass-Through IE11', () => {
const appDir = join(fixturesDir, 'cp-ie-11')
@ -134,124 +70,6 @@ describe('Custom Properties: Pass-Through Modern', () => {
})
})
describe('Custom Properties: Fail for :root {} in CSS Modules', () => {
const appDir = join(fixturesDir, 'cp-global-modules')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('pages/styles.module.css')
expect(stderr).toContain('Selector ":root" is not pure')
})
})
describe('Custom Properties: Fail for global element in CSS Modules', () => {
const appDir = join(fixturesDir, 'cp-el-modules')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('pages/styles.module.css')
expect(stderr).toContain('Selector "h1" is not pure')
})
})
describe('CSS Modules: Import Global CSS', () => {
const appDir = join(fixturesDir, 'module-import-global')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"a .styles_foo__Io_Us{all:initial}"`
)
})
})
describe('CSS Modules: Importing Invalid Global CSS', () => {
const appDir = join(fixturesDir, 'module-import-global-invalid')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('pages/styles.css')
expect(stderr).toContain('Selector "a" is not pure')
})
})
describe('CSS Modules: Import Exports', () => {
const appDir = join(fixturesDir, 'module-import-exports')
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".styles_blk__CqbFg{color:#000}"`
)
})
})
describe('Inline Comments: Minify', () => {
const appDir = join(fixturesDir, 'inline-comments')

View file

@ -0,0 +1,317 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('Basic Global Support', () => {
const appDir = join(fixturesDir, 'single-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Global Support with special characters in path', () => {
const appDir = join(fixturesDir, 'single-global-special-characters', 'a+b')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Global Support with src/ dir', () => {
const appDir = join(fixturesDir, 'single-global-src')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Multi Global Support', () => {
const appDir = join(fixturesDir, 'multi-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".red-text{color:red}.blue-text{color:blue}"`
)
})
})
describe('Nested @import() Global Support', () => {
const appDir = join(fixturesDir, 'nested-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".red-text{color:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:blue}"`
)
})
})
// Tests css ordering
describe('Multi Global Support (reversed)', () => {
const appDir = join(fixturesDir, 'multi-global-reversed')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".blue-text{color:blue}.red-text{color:red}"`
)
})
})
describe('CSS URL via `file-loader`', () => {
const appDir = join(fixturesDir, 'url-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted expected files`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const mediaFolder = join(appDir, '.next/static/media')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/^\.red-text\{color:red;background-image:url\(\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/
)
const mediaFiles = await readdir(mediaFolder)
expect(mediaFiles.length).toBe(3)
expect(
mediaFiles
.map((fileName) =>
/^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.')
)
.sort()
).toMatchInlineSnapshot(`
Array [
"dark.svg",
"dark2.svg",
"light.svg",
]
`)
})
})
describe('CSS URL via `file-loader` and asset prefix (1)', () => {
const appDir = join(fixturesDir, 'url-global-asset-prefix-1')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted expected files`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const mediaFolder = join(appDir, '.next/static/media')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/
)
const mediaFiles = await readdir(mediaFolder)
expect(mediaFiles.length).toBe(3)
expect(
mediaFiles
.map((fileName) =>
/^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.')
)
.sort()
).toMatchInlineSnapshot(`
Array [
"dark.svg",
"dark2.svg",
"light.svg",
]
`)
})
})
describe('CSS URL via `file-loader` and asset prefix (2)', () => {
const appDir = join(fixturesDir, 'url-global-asset-prefix-2')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted expected files`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const mediaFolder = join(appDir, '.next/static/media')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/
)
const mediaFiles = await readdir(mediaFolder)
expect(mediaFiles.length).toBe(3)
expect(
mediaFiles
.map((fileName) =>
/^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.')
)
.sort()
).toMatchInlineSnapshot(`
Array [
"dark.svg",
"dark2.svg",
"light.svg",
]
`)
})
})

View file

@ -0,0 +1,72 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { remove } from 'fs-extra'
import {
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('Ordering with styled-jsx (dev)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
describe('Ordering with styled-jsx (prod)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})

View file

@ -0,0 +1,288 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import {
findPort,
killApp,
nextBuild,
nextStart,
renderViaHTTP,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('CSS Support', () => {
describe('CSS Compilation and Prefixing', () => {
const appDir = join(fixturesDir, 'compilation-and-prefixing')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've compiled and prefixed`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`"@media (min-width:480px) and (max-width:767px){::placeholder{color:green}}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}.transform-parsing{transform:translate3d(0,0)}.css-grid-shorthand{grid-column:span 2}.g-docs-sidenav .filter::-webkit-input-placeholder{opacity:80%}"`
)
// Contains a source map
expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
})
it(`should've emitted a source map`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssMapFiles = files.filter((f) => /\.css\.map$/.test(f))
expect(cssMapFiles.length).toBe(1)
const cssMapContent = (
await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
).trim()
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
Object {
"mappings": "AAAA,+CACE,cACE,WACF,CACF,CAEA,cACE,2CACF,CAEA,mBACE,0BACF,CAEA,oBACE,kBACF,CAEA,mDACE,WACF",
"sourcesContent": Array [
"@media (480px <= width < 768px) {
::placeholder {
color: green;
}
}
.flex-parsing {
flex: 0 0 calc(50% - var(--vertical-gutter));
}
.transform-parsing {
transform: translate3d(0px, 0px);
}
.css-grid-shorthand {
grid-column: span 2;
}
.g-docs-sidenav .filter::-webkit-input-placeholder {
opacity: 80%;
}
",
],
"version": 3,
}
`)
})
})
describe('React Lifecyce Order (production)', () => {
const appDir = join(fixturesDir, 'transition-react')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
let code
let stdout
beforeAll(async () => {
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color on mount after navigation', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
const text = await browser.waitForElementByCss('#red-title').text()
expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Production', () => {
const appDir = join(fixturesDir, 'multi-page')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have CSS for page', async () => {
const browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it(`should've preloaded the CSS file and injected it in <head>`, async () => {
const content = await renderViaHTTP(appPort, '/page2')
const $ = cheerio.load(content)
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
/* ensure CSS preloaded first */
const allPreloads = [].slice.call($('link[rel="preload"]'))
const styleIndexes = allPreloads.flatMap((p, i) =>
p.attribs.as === 'style' ? i : []
)
expect(styleIndexes).toEqual([0])
})
})
describe('Good CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(/nprogress/)
})
})
describe('Good Nested CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`)
})
})
})
// https://github.com/vercel/next.js/issues/15468
describe('CSS Property Ordering', () => {
const appDir = join(fixturesDir, 'next-issue-15468')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the border width (property ordering)', async () => {
const browser = await webdriver(appPort, '/')
const width1 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test1')).borderWidth`
)
expect(width1).toMatchInlineSnapshot(`"0px"`)
const width2 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test2')).borderWidth`
)
expect(width2).toMatchInlineSnapshot(`"5px"`)
})
})

View file

@ -0,0 +1,455 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import {
check,
File,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
// https://github.com/vercel/next.js/issues/12343
describe('Basic CSS Modules Ordering', () => {
const appDir = join(fixturesDir, 'next-issue-12343')
let app, appPort
function tests() {
async function checkGreenButton(browser) {
await browser.waitForElementByCss('#link-other')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#link-other')).backgroundColor`
)
expect(titleColor).toBe('rgb(0, 255, 0)')
}
async function checkPinkButton(browser) {
await browser.waitForElementByCss('#link-index')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#link-index')).backgroundColor`
)
expect(titleColor).toBe('rgb(255, 105, 180)')
}
it('should have correct color on index page (on load)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenButton(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on hover)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenButton(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkGreenButton(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenButton(browser)
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
await checkPinkButton(browser)
// Navigate back to index:
await browser.waitForElementByCss('#link-index').click()
await checkGreenButton(browser)
} finally {
await browser.close()
}
})
}
describe('Development Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('should handle unresolved files gracefully', () => {
const workDir = join(fixturesDir, 'unresolved-css-url')
it('should build correctly', async () => {
await remove(join(workDir, '.next'))
const { code } = await nextBuild(workDir)
expect(code).toBe(0)
})
it('should have correct file references in CSS output', async () => {
const cssFiles = await readdir(join(workDir, '.next/static/css'))
for (const file of cssFiles) {
if (file.endsWith('.css.map')) continue
const content = await readFile(
join(workDir, '.next/static/css', file),
'utf8'
)
console.log(file, content)
// if it is the combined global CSS file there are double the expected
// results
const howMany = content.includes('p{') || content.includes('p,') ? 2 : 1
expect(content.match(/\(\/vercel\.svg/g).length).toBe(howMany)
// expect(content.match(/\(vercel\.svg/g).length).toBe(howMany)
expect(content.match(/\(\/_next\/static\/media/g).length).toBe(1)
expect(content.match(/\(https:\/\//g).length).toBe(howMany)
}
})
})
describe('Data URLs', () => {
const workDir = join(fixturesDir, 'data-url')
it('should compile successfully', async () => {
await remove(join(workDir, '.next'))
const { code } = await nextBuild(workDir)
expect(code).toBe(0)
})
it('should have emitted expected files', async () => {
const cssFolder = join(workDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/background:url\("data:[^"]+"\)/
)
})
})
describe('Ordering with Global CSS and Modules (dev)', () => {
const appDir = join(fixturesDir, 'global-and-module-ordering')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should not execute scripts in any order', async () => {
const content = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(content)
let asyncCount = 0
let totalCount = 0
for (const script of $('script').toArray()) {
++totalCount
if ('async' in script.attribs) {
++asyncCount
}
}
expect(asyncCount).toBe(0)
expect(totalCount).not.toBe(0)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it('should have the correct color (css ordering) during hot reloads', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
const blueColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
)
expect(blueColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
const yellowColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#yellowText')).color`
)
expect(yellowColor).toMatchInlineSnapshot(`"rgb(255, 255, 0)"`)
const cssFile = new File(join(appDir, 'pages/index.module.css'))
try {
cssFile.replace('color: yellow;', 'color: rgb(1, 1, 1);')
await check(
() =>
browser.eval(
`window.getComputedStyle(document.querySelector('#yellowText')).color`
),
'rgb(1, 1, 1)'
)
await check(
() =>
browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
),
'rgb(0, 0, 255)'
)
} finally {
cssFile.restore()
}
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Ordering with Global CSS and Modules (prod)', () => {
const appDir = join(fixturesDir, 'global-and-module-ordering')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
})
// https://github.com/vercel/next.js/issues/12445
describe('CSS Modules Composes Ordering', () => {
const appDir = join(fixturesDir, 'composes-ordering')
let app, appPort
function tests(isDev = false) {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
async function checkRedTitle(browser) {
await browser.waitForElementByCss('#red-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#red-title')).color`
)
expect(titleColor).toBe('rgb(255, 0, 0)')
}
it('should have correct color on index page (on load)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on hover)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
if (!isDev) {
it('should not change color on hover', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct CSS injection order', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
const prevSiblingHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').previousSibling.getAttribute('href')`
)
const currentPageHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').getAttribute('href')`
)
expect(prevSiblingHref).toBeDefined()
expect(prevSiblingHref).toBe(currentPageHref)
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
await checkRedTitle(browser)
const newPrevSibling = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling).toBe('')
expect(newPageHref).toBeDefined()
expect(newPageHref).not.toBe(currentPageHref)
// Navigate to home:
await browser.waitForElementByCss('#link-index').click()
await checkBlackTitle(browser)
const newPrevSibling2 = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref2 = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling2).toBe('')
expect(newPageHref2).toBeDefined()
expect(newPageHref2).toBe(currentPageHref)
} finally {
await browser.close()
}
})
}
it('should have correct color on index page (on nav from index)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
await checkRedTitle(browser)
// Navigate back to index:
await browser.waitForElementByCss('#link-index').click()
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav from other)', async () => {
const browser = await webdriver(appPort, '/other')
try {
await checkRedTitle(browser)
await browser.waitForElementByCss('#link-index').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-other')
await checkBlackTitle(browser)
// Navigate back to other:
await browser.waitForElementByCss('#link-other').click()
await checkRedTitle(browser)
} finally {
await browser.close()
}
})
}
describe('Development Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests(true)
})
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})

View file

@ -0,0 +1,406 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { pathExists, readFile, readJSON, remove } from 'fs-extra'
import {
check,
findPort,
killApp,
nextBuild,
nextStart,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('CSS Support', () => {
describe('CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-bad')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail the build', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(0)
expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
expect(stderr).not.toMatch(/Build error occurred/)
})
})
// https://github.com/vercel/next.js/issues/18557
describe('CSS page transition inject <style> with nonce so it works with CSP header', () => {
const appDir = join(fixturesDir, 'csp-style-src-nonce')
let app, appPort
function tests() {
async function checkGreenTitle(browser) {
await browser.waitForElementByCss('#green-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#green-title')).color`
)
expect(titleColor).toBe('rgb(0, 128, 0)')
}
async function checkBlueTitle(browser) {
await browser.waitForElementByCss('#blue-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blue-title')).color`
)
expect(titleColor).toBe('rgb(0, 0, 255)')
}
it('should have correct color on index page (on load)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on hover)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should not change color on hover', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct CSS injection order', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
const prevSiblingHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').previousSibling.getAttribute('href')`
)
const currentPageHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').getAttribute('href')`
)
expect(prevSiblingHref).toBeDefined()
expect(prevSiblingHref).toBe(currentPageHref)
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
await checkBlueTitle(browser)
const newPrevSibling = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling).toBe('VmVyY2Vs')
expect(newPageHref).toBeDefined()
expect(newPageHref).not.toBe(currentPageHref)
// Navigate to home:
await browser.waitForElementByCss('#link-index').click()
await checkGreenTitle(browser)
const newPrevSibling2 = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref2 = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling2).toBeTruthy()
expect(newPageHref2).toBeDefined()
expect(newPageHref2).toBe(currentPageHref)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav from index)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
await checkBlueTitle(browser)
// Navigate back to index:
await browser.waitForElementByCss('#link-index').click()
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav from other)', async () => {
const browser = await webdriver(appPort, '/other')
try {
await checkBlueTitle(browser)
await browser.waitForElementByCss('#link-index').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-other')
await checkGreenTitle(browser)
// Navigate back to other:
await browser.waitForElementByCss('#link-other').click()
await checkBlueTitle(browser)
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('CSS Cleanup on Render Failure', () => {
const appDir = join(fixturesDir, 'transition-cleanup')
let app, appPort
function tests() {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
it('not have intermediary page styles on error rendering', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
const currentPageStyles = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]')`
)
expect(currentPageStyles).toBeDefined()
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
await check(
() => browser.eval(`document.body.innerText`),
'Application error: a client-side exception has occurred (see the browser console for more information).',
true
)
const newPageStyles = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]')`
)
expect(newPageStyles).toBeFalsy()
const allPageStyles = await browser.eval(
`document.querySelector('link[rel=stylesheet]')`
)
expect(allPageStyles).toBeFalsy()
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('Page reload on CSS missing', () => {
const appDir = join(fixturesDir, 'transition-reload')
let app, appPort
function tests() {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
it('should fall back to server-side transition on missing CSS', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.eval(`window.__priorNavigatePageState = 'OOF';`)
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
const state = await browser.eval(`window.__priorNavigatePageState`)
expect(state).toBeFalsy()
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
// Remove other page CSS files:
const manifest = await readJSON(
join(appDir, '.next', 'build-manifest.json')
)
const files = manifest['pages']['/other'].filter((e) =>
e.endsWith('.css')
)
if (files.length < 1) throw new Error()
await Promise.all(files.map((f) => remove(join(appDir, '.next', f))))
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('Page hydrates with CSS and not waiting on dependencies', () => {
const appDir = join(fixturesDir, 'hydrate-without-deps')
let app, appPort
function tests() {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
async function checkRedTitle(browser) {
await browser.waitForElementByCss('#red-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#red-title')).color`
)
expect(titleColor).toBe('rgb(255, 0, 0)')
}
it('should hydrate black without dependencies manifest', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
} finally {
await browser.close()
}
})
it('should hydrate red without dependencies manifest', async () => {
const browser = await webdriver(appPort, '/client')
try {
await checkRedTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
} finally {
await browser.close()
}
})
it('should route from black to red without dependencies', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
await browser.eval(`document.querySelector('#link-client').click()`)
await checkRedTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
const buildId = (
await readFile(join(appDir, '.next', 'BUILD_ID'), 'utf8')
).trim()
const fileName = join(
appDir,
'.next/static/',
buildId,
'_buildManifest.js'
)
if (!(await pathExists(fileName))) {
throw new Error('Missing build manifest')
}
await remove(fileName)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
})

View file

@ -0,0 +1,210 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { remove } from 'fs-extra'
import {
check,
File,
findPort,
killApp,
launchApp,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('Can hot reload CSS without losing state', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should update CSS color without remounting <input>', async () => {
let browser
try {
browser = await webdriver(appPort, '/page1')
const desiredText = 'hello world'
await browser.elementById('text-input').type(desiredText)
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
const cssFile = new File(join(appDir, 'styles/global1.css'))
try {
cssFile.replace('color: red', 'color: purple')
await check(
() =>
browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
),
'rgb(128, 0, 128)'
)
// ensure text remained
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
} finally {
cssFile.restore()
}
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Development', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have CSS for page', async () => {
let browser
try {
browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when unused in Development', () => {
const appDir = join(fixturesDir, 'unused')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when broken in Development', () => {
const appDir = join(fixturesDir, 'unused')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
const pageFile = new File(join(appDir, 'pages/index.js'))
let browser
try {
pageFile.replace('<div />', '<div>')
await waitFor(2000) // wait for recompile
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
pageFile.restore()
if (browser) {
await browser.close()
}
}
})
})
describe('React Lifecyce Order (dev)', () => {
const appDir = join(fixturesDir, 'transition-react')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color on mount after navigation', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
const text = await browser.waitForElementByCss('#red-title').text()
expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})

View file

@ -1,870 +0,0 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import {
check,
File,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('CSS Support', () => {
describe('Basic Global Support', () => {
const appDir = join(fixturesDir, 'single-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Global Support with special characters in path', () => {
const appDir = join(fixturesDir, 'single-global-special-characters', 'a+b')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Global Support with src/ dir', () => {
const appDir = join(fixturesDir, 'single-global-src')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Multi Global Support', () => {
const appDir = join(fixturesDir, 'multi-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".red-text{color:red}.blue-text{color:blue}"`)
})
})
describe('Nested @import() Global Support', () => {
const appDir = join(fixturesDir, 'nested-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".red-text{color:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:blue}"`
)
})
})
describe('CSS Compilation and Prefixing', () => {
const appDir = join(fixturesDir, 'compilation-and-prefixing')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've compiled and prefixed`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`"@media (min-width:480px) and (max-width:767px){::placeholder{color:green}}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}.transform-parsing{transform:translate3d(0,0)}.css-grid-shorthand{grid-column:span 2}.g-docs-sidenav .filter::-webkit-input-placeholder{opacity:80%}"`
)
// Contains a source map
expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
})
it(`should've emitted a source map`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssMapFiles = files.filter((f) => /\.css\.map$/.test(f))
expect(cssMapFiles.length).toBe(1)
const cssMapContent = (
await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
).trim()
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
Object {
"mappings": "AAAA,+CACE,cACE,WACF,CACF,CAEA,cACE,2CACF,CAEA,mBACE,0BACF,CAEA,oBACE,kBACF,CAEA,mDACE,WACF",
"sourcesContent": Array [
"@media (480px <= width < 768px) {
::placeholder {
color: green;
}
}
.flex-parsing {
flex: 0 0 calc(50% - var(--vertical-gutter));
}
.transform-parsing {
transform: translate3d(0px, 0px);
}
.css-grid-shorthand {
grid-column: span 2;
}
.g-docs-sidenav .filter::-webkit-input-placeholder {
opacity: 80%;
}
",
],
"version": 3,
}
`)
})
})
// Tests css ordering
describe('Multi Global Support (reversed)', () => {
const appDir = join(fixturesDir, 'multi-global-reversed')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".blue-text{color:blue}.red-text{color:red}"`)
})
})
describe('React Lifecyce Order (dev)', () => {
const appDir = join(fixturesDir, 'transition-react')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color on mount after navigation', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
const text = await browser.waitForElementByCss('#red-title').text()
expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('React Lifecyce Order (production)', () => {
const appDir = join(fixturesDir, 'transition-react')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
let code
let stdout
beforeAll(async () => {
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color on mount after navigation', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
const text = await browser.waitForElementByCss('#red-title').text()
expect(text).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Invalid CSS in _document', () => {
const appDir = join(fixturesDir, 'invalid-module-document')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles.module.css')
expect(stderr).toMatch(
/CSS.*cannot.*be imported within.*pages[\\/]_document\.js/
)
expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
})
})
describe('Invalid Global CSS', () => {
const appDir = join(fixturesDir, 'invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.css')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Valid Global CSS from npm', () => {
const appDir = join(fixturesDir, 'import-global-from-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".red-text{color:\\"red\\"}"`)
})
})
describe('Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'invalid-global-with-app')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.css')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Valid and Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'valid-and-invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.css')
expect(stderr).toContain('Please move all first-party global CSS imports')
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Can hot reload CSS without losing state', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should update CSS color without remounting <input>', async () => {
let browser
try {
browser = await webdriver(appPort, '/page1')
const desiredText = 'hello world'
await browser.elementById('text-input').type(desiredText)
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
const cssFile = new File(join(appDir, 'styles/global1.css'))
try {
cssFile.replace('color: red', 'color: purple')
await check(
() =>
browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
),
'rgb(128, 0, 128)'
)
// ensure text remained
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
} finally {
cssFile.restore()
}
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Development', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have CSS for page', async () => {
let browser
try {
browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when unused in Development', () => {
const appDir = join(fixturesDir, 'unused')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when broken in Development', () => {
const appDir = join(fixturesDir, 'unused')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
const pageFile = new File(join(appDir, 'pages/index.js'))
let browser
try {
pageFile.replace('<div />', '<div>')
await waitFor(2000) // wait for recompile
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
pageFile.restore()
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Production', () => {
const appDir = join(fixturesDir, 'multi-page')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have CSS for page', async () => {
const browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it(`should've preloaded the CSS file and injected it in <head>`, async () => {
const content = await renderViaHTTP(appPort, '/page2')
const $ = cheerio.load(content)
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
/* ensure CSS preloaded first */
const allPreloads = [].slice.call($('link[rel="preload"]'))
const styleIndexes = allPreloads.flatMap((p, i) =>
p.attribs.as === 'style' ? i : []
)
expect(styleIndexes).toEqual([0])
})
})
describe('CSS URL via `file-loader`', () => {
const appDir = join(fixturesDir, 'url-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted expected files`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const mediaFolder = join(appDir, '.next/static/media')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/^\.red-text\{color:red;background-image:url\(\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/
)
const mediaFiles = await readdir(mediaFolder)
expect(mediaFiles.length).toBe(3)
expect(
mediaFiles
.map((fileName) =>
/^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.')
)
.sort()
).toMatchInlineSnapshot(`
Array [
"dark.svg",
"dark2.svg",
"light.svg",
]
`)
})
})
describe('CSS URL via `file-loader` and asset prefix (1)', () => {
const appDir = join(fixturesDir, 'url-global-asset-prefix-1')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted expected files`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const mediaFolder = join(appDir, '.next/static/media')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/
)
const mediaFiles = await readdir(mediaFolder)
expect(mediaFiles.length).toBe(3)
expect(
mediaFiles
.map((fileName) =>
/^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.')
)
.sort()
).toMatchInlineSnapshot(`
Array [
"dark.svg",
"dark2.svg",
"light.svg",
]
`)
})
})
describe('CSS URL via `file-loader` and asset prefix (2)', () => {
const appDir = join(fixturesDir, 'url-global-asset-prefix-2')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted expected files`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const mediaFolder = join(appDir, '.next/static/media')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/^\.red-text\{color:red;background-image:url\(\/foo\/_next\/static\/media\/dark\.[a-f0-9]{8}\.svg\) url\(\/foo\/_next\/static\/media\/dark2\.[a-f0-9]{8}\.svg\)\}\.blue-text\{color:orange;font-weight:bolder;background-image:url\(\/foo\/_next\/static\/media\/light\.[a-f0-9]{8}\.svg\);color:blue\}$/
)
const mediaFiles = await readdir(mediaFolder)
expect(mediaFiles.length).toBe(3)
expect(
mediaFiles
.map((fileName) =>
/^(.+?)\..{8}\.(.+?)$/.exec(fileName).slice(1).join('.')
)
.sort()
).toMatchInlineSnapshot(`
Array [
"dark.svg",
"dark2.svg",
"light.svg",
]
`)
})
})
describe('Good CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(/nprogress/)
})
})
describe('Good Nested CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`)
})
})
})

View file

@ -1,944 +0,0 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import 'flat-map-polyfill'
import { pathExists, readdir, readFile, readJSON, remove } from 'fs-extra'
import {
check,
File,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('CSS Support', () => {
describe('CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-bad')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail the build', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(0)
expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
expect(stderr).not.toMatch(/Build error occurred/)
})
})
describe('Ordering with styled-jsx (dev)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
describe('Ordering with styled-jsx (prod)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
describe('Ordering with Global CSS and Modules (dev)', () => {
const appDir = join(fixturesDir, 'global-and-module-ordering')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should not execute scripts in any order', async () => {
const content = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(content)
let asyncCount = 0
let totalCount = 0
for (const script of $('script').toArray()) {
++totalCount
if ('async' in script.attribs) {
++asyncCount
}
}
expect(asyncCount).toBe(0)
expect(totalCount).not.toBe(0)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it('should have the correct color (css ordering) during hot reloads', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
const blueColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
)
expect(blueColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
const yellowColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#yellowText')).color`
)
expect(yellowColor).toMatchInlineSnapshot(`"rgb(255, 255, 0)"`)
const cssFile = new File(join(appDir, 'pages/index.module.css'))
try {
cssFile.replace('color: yellow;', 'color: rgb(1, 1, 1);')
await check(
() =>
browser.eval(
`window.getComputedStyle(document.querySelector('#yellowText')).color`
),
'rgb(1, 1, 1)'
)
await check(
() =>
browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
),
'rgb(0, 0, 255)'
)
} finally {
cssFile.restore()
}
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Ordering with Global CSS and Modules (prod)', () => {
const appDir = join(fixturesDir, 'global-and-module-ordering')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blueText')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
})
// https://github.com/vercel/next.js/issues/15468
describe('CSS Property Ordering', () => {
const appDir = join(fixturesDir, 'next-issue-15468')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the border width (property ordering)', async () => {
const browser = await webdriver(appPort, '/')
const width1 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test1')).borderWidth`
)
expect(width1).toMatchInlineSnapshot(`"0px"`)
const width2 = await browser.eval(
`window.getComputedStyle(document.querySelector('.test2')).borderWidth`
)
expect(width2).toMatchInlineSnapshot(`"5px"`)
})
})
// https://github.com/vercel/next.js/issues/18557
describe('CSS page transition inject <style> with nonce so it works with CSP header', () => {
const appDir = join(fixturesDir, 'csp-style-src-nonce')
let app, appPort
function tests() {
async function checkGreenTitle(browser) {
await browser.waitForElementByCss('#green-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#green-title')).color`
)
expect(titleColor).toBe('rgb(0, 128, 0)')
}
async function checkBlueTitle(browser) {
await browser.waitForElementByCss('#blue-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#blue-title')).color`
)
expect(titleColor).toBe('rgb(0, 0, 255)')
}
it('should have correct color on index page (on load)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on hover)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should not change color on hover', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct CSS injection order', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
const prevSiblingHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').previousSibling.getAttribute('href')`
)
const currentPageHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').getAttribute('href')`
)
expect(prevSiblingHref).toBeDefined()
expect(prevSiblingHref).toBe(currentPageHref)
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
await checkBlueTitle(browser)
const newPrevSibling = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling).toBe('VmVyY2Vs')
expect(newPageHref).toBeDefined()
expect(newPageHref).not.toBe(currentPageHref)
// Navigate to home:
await browser.waitForElementByCss('#link-index').click()
await checkGreenTitle(browser)
const newPrevSibling2 = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref2 = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling2).toBeTruthy()
expect(newPageHref2).toBeDefined()
expect(newPageHref2).toBe(currentPageHref)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav from index)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenTitle(browser)
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
await checkBlueTitle(browser)
// Navigate back to index:
await browser.waitForElementByCss('#link-index').click()
await checkGreenTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav from other)', async () => {
const browser = await webdriver(appPort, '/other')
try {
await checkBlueTitle(browser)
await browser.waitForElementByCss('#link-index').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-other')
await checkGreenTitle(browser)
// Navigate back to other:
await browser.waitForElementByCss('#link-other').click()
await checkBlueTitle(browser)
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
// https://github.com/vercel/next.js/issues/12445
describe('CSS Modules Composes Ordering', () => {
const appDir = join(fixturesDir, 'composes-ordering')
let app, appPort
function tests(isDev = false) {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
async function checkRedTitle(browser) {
await browser.waitForElementByCss('#red-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#red-title')).color`
)
expect(titleColor).toBe('rgb(255, 0, 0)')
}
it('should have correct color on index page (on load)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on hover)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
if (!isDev) {
it('should not change color on hover', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct CSS injection order', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
const prevSiblingHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').previousSibling.getAttribute('href')`
)
const currentPageHref = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]').getAttribute('href')`
)
expect(prevSiblingHref).toBeDefined()
expect(prevSiblingHref).toBe(currentPageHref)
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
await checkRedTitle(browser)
const newPrevSibling = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling).toBe('')
expect(newPageHref).toBeDefined()
expect(newPageHref).not.toBe(currentPageHref)
// Navigate to home:
await browser.waitForElementByCss('#link-index').click()
await checkBlackTitle(browser)
const newPrevSibling2 = await browser.eval(
`document.querySelector('style[data-n-href]').previousSibling.getAttribute('data-n-css')`
)
const newPageHref2 = await browser.eval(
`document.querySelector('style[data-n-href]').getAttribute('data-n-href')`
)
expect(newPrevSibling2).toBe('')
expect(newPageHref2).toBeDefined()
expect(newPageHref2).toBe(currentPageHref)
} finally {
await browser.close()
}
})
}
it('should have correct color on index page (on nav from index)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
await checkRedTitle(browser)
// Navigate back to index:
await browser.waitForElementByCss('#link-index').click()
await checkBlackTitle(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav from other)', async () => {
const browser = await webdriver(appPort, '/other')
try {
await checkRedTitle(browser)
await browser.waitForElementByCss('#link-index').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-other')
await checkBlackTitle(browser)
// Navigate back to other:
await browser.waitForElementByCss('#link-other').click()
await checkRedTitle(browser)
} finally {
await browser.close()
}
})
}
describe('Development Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests(true)
})
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('CSS Cleanup on Render Failure', () => {
const appDir = join(fixturesDir, 'transition-cleanup')
let app, appPort
function tests() {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
it('not have intermediary page styles on error rendering', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
const currentPageStyles = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]')`
)
expect(currentPageStyles).toBeDefined()
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
await check(
() => browser.eval(`document.body.innerText`),
'Application error: a client-side exception has occurred (see the browser console for more information).',
true
)
const newPageStyles = await browser.eval(
`document.querySelector('link[rel=stylesheet][data-n-p]')`
)
expect(newPageStyles).toBeFalsy()
const allPageStyles = await browser.eval(
`document.querySelector('link[rel=stylesheet]')`
)
expect(allPageStyles).toBeFalsy()
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('Page reload on CSS missing', () => {
const appDir = join(fixturesDir, 'transition-reload')
let app, appPort
function tests() {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
it('should fall back to server-side transition on missing CSS', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await browser.eval(`window.__priorNavigatePageState = 'OOF';`)
// Navigate to other:
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
const state = await browser.eval(`window.__priorNavigatePageState`)
expect(state).toBeFalsy()
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
// Remove other page CSS files:
const manifest = await readJSON(
join(appDir, '.next', 'build-manifest.json')
)
const files = manifest['pages']['/other'].filter((e) =>
e.endsWith('.css')
)
if (files.length < 1) throw new Error()
await Promise.all(files.map((f) => remove(join(appDir, '.next', f))))
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('Page hydrates with CSS and not waiting on dependencies', () => {
const appDir = join(fixturesDir, 'hydrate-without-deps')
let app, appPort
function tests() {
async function checkBlackTitle(browser) {
await browser.waitForElementByCss('#black-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#black-title')).color`
)
expect(titleColor).toBe('rgb(17, 17, 17)')
}
async function checkRedTitle(browser) {
await browser.waitForElementByCss('#red-title')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#red-title')).color`
)
expect(titleColor).toBe('rgb(255, 0, 0)')
}
it('should hydrate black without dependencies manifest', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
} finally {
await browser.close()
}
})
it('should hydrate red without dependencies manifest', async () => {
const browser = await webdriver(appPort, '/client')
try {
await checkRedTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
} finally {
await browser.close()
}
})
it('should route from black to red without dependencies', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkBlackTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
await browser.eval(`document.querySelector('#link-client').click()`)
await checkRedTitle(browser)
await check(
() => browser.eval(`document.querySelector('p').innerText`),
'mounted'
)
} finally {
await browser.close()
}
})
}
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
const buildId = (
await readFile(join(appDir, '.next', 'BUILD_ID'), 'utf8')
).trim()
const fileName = join(
appDir,
'.next/static/',
buildId,
'_buildManifest.js'
)
if (!(await pathExists(fileName))) {
throw new Error('Missing build manifest')
}
await remove(fileName)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
// https://github.com/vercel/next.js/issues/12343
describe('Basic CSS Modules Ordering', () => {
const appDir = join(fixturesDir, 'next-issue-12343')
let app, appPort
function tests() {
async function checkGreenButton(browser) {
await browser.waitForElementByCss('#link-other')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#link-other')).backgroundColor`
)
expect(titleColor).toBe('rgb(0, 255, 0)')
}
async function checkPinkButton(browser) {
await browser.waitForElementByCss('#link-index')
const titleColor = await browser.eval(
`window.getComputedStyle(document.querySelector('#link-index')).backgroundColor`
)
expect(titleColor).toBe('rgb(255, 105, 180)')
}
it('should have correct color on index page (on load)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenButton(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on hover)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenButton(browser)
await browser.waitForElementByCss('#link-other').moveTo()
await waitFor(2000)
await checkGreenButton(browser)
} finally {
await browser.close()
}
})
it('should have correct color on index page (on nav)', async () => {
const browser = await webdriver(appPort, '/')
try {
await checkGreenButton(browser)
await browser.waitForElementByCss('#link-other').click()
// Wait for navigation:
await browser.waitForElementByCss('#link-index')
await checkPinkButton(browser)
// Navigate back to index:
await browser.waitForElementByCss('#link-index').click()
await checkGreenButton(browser)
} finally {
await browser.close()
}
})
}
describe('Development Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
describe('Production Mode', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
beforeAll(async () => {
await nextBuild(appDir, [], {})
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
tests()
})
})
describe('should handle unresolved files gracefully', () => {
const workDir = join(fixturesDir, 'unresolved-css-url')
it('should build correctly', async () => {
await remove(join(workDir, '.next'))
const { code } = await nextBuild(workDir)
expect(code).toBe(0)
})
it('should have correct file references in CSS output', async () => {
const cssFiles = await readdir(join(workDir, '.next/static/css'))
for (const file of cssFiles) {
if (file.endsWith('.css.map')) continue
const content = await readFile(
join(workDir, '.next/static/css', file),
'utf8'
)
console.log(file, content)
// if it is the combined global CSS file there are double the expected
// results
const howMany = content.includes('p{') || content.includes('p,') ? 2 : 1
expect(content.match(/\(\/vercel\.svg/g).length).toBe(howMany)
// expect(content.match(/\(vercel\.svg/g).length).toBe(howMany)
expect(content.match(/\(\/_next\/static\/media/g).length).toBe(1)
expect(content.match(/\(https:\/\//g).length).toBe(howMany)
}
})
})
describe('Data URLs', () => {
const workDir = join(fixturesDir, 'data-url')
it('should compile successfully', async () => {
await remove(join(workDir, '.next'))
const { code } = await nextBuild(workDir)
expect(code).toBe(0)
})
it('should have emitted expected files', async () => {
const cssFolder = join(workDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(
/background:url\("data:[^"]+"\)/
)
})
})
})

View file

@ -0,0 +1,118 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'css-fixtures')
describe('Invalid CSS in _document', () => {
const appDir = join(fixturesDir, 'invalid-module-document')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles.module.css')
expect(stderr).toMatch(
/CSS.*cannot.*be imported within.*pages[\\/]_document\.js/
)
expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
})
})
describe('Invalid Global CSS', () => {
const appDir = join(fixturesDir, 'invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.css')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Valid Global CSS from npm', () => {
const appDir = join(fixturesDir, 'import-global-from-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".red-text{color:\\"red\\"}"`
)
})
})
describe('Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'invalid-global-with-app')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.css')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Valid and Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'valid-and-invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.css')
expect(stderr).toContain('Please move all first-party global CSS imports')
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})

View file

@ -1,41 +1,30 @@
/* eslint-disable jest/no-identical-title */
/* eslint-env jest */
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { remove } from 'fs-extra'
import { join } from 'path'
import {
check,
fetchViaHTTP,
File,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
} from 'next-test-utils'
import {
appOption,
context,
expectModuleNotFoundDevError,
expectModuleNotFoundProdError,
expectNoError,
expectUnsupportedModuleDevError,
expectUnsupportedModuleProdError,
getUnsupportedModuleWarning,
} from './utils'
jest.setTimeout(1000 * 60 * 2)
const context = {
appDir: join(__dirname, '../'),
logs: { output: '', stdout: '', stderr: '' },
api: new File(join(__dirname, '../pages/api/route.js')),
lib: new File(join(__dirname, '../lib.js')),
middleware: new File(join(__dirname, '../middleware.js')),
page: new File(join(__dirname, '../pages/index.js')),
}
const appOption = {
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
onStdout(msg) {
context.logs.output += msg
context.logs.stdout += msg
},
onStderr(msg) {
context.logs.output += msg
context.logs.stderr += msg
},
}
const routeUrl = '/api/route'
const middlewareUrl = '/'
@ -56,70 +45,6 @@ describe('Edge runtime code with imports', () => {
context.page.restore()
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
${importStatement}
export default async function handler(request) {
basename()
return Response.json({ ok: basename() })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
${importStatement}
export async function middleware(request) {
basename()
return NextResponse.next()
}
`)
},
},
])('$title statically importing node.js module', ({ init, url }) => {
const moduleName = 'path'
const importStatement = `import { basename } from "${moduleName}"`
beforeEach(() => init(importStatement))
it('throws unsupported module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectUnsupportedModuleDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
})
describe.each([
{
title: 'Edge API',
@ -328,190 +253,6 @@ describe('Edge runtime code with imports', () => {
})
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
export default async function handler(request) {
new (${importStatement})()
return Response.json({ ok: true })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
export async function middleware(request) {
new (${importStatement})()
return NextResponse.next()
}
`)
},
},
])('$title dynamically importing 3rd party module', ({ init, url }) => {
const moduleName = 'not-exist'
const importStatement = `await import("${moduleName}")`
beforeEach(() => init(importStatement))
it('throws not-found module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectModuleNotFoundDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
export default async function handler(request) {
if (process.env === 'production') {
new (${importStatement})()
}
return Response.json({ ok: true })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
export async function middleware(request) {
if (process.env === 'production') {
new (${importStatement})()
}
return NextResponse.next()
}
`)
},
},
])('$title importing unused 3rd party module', ({ init, url }) => {
const moduleName = 'not-exist'
const importStatement = `await import("${moduleName}")`
beforeEach(() => init(importStatement))
it('throws not-found module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectModuleNotFoundDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
export default async function handler(request) {
if (process.env === 'production') {
(${importStatement}).spawn('ls', ['-lh', '/usr'])
}
return Response.json({ ok: true })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
export async function middleware(request) {
if (process.env === 'production') {
(${importStatement}).spawn('ls', ['-lh', '/usr'])
}
return NextResponse.next()
}
`)
},
},
])('$title importing unused node.js module', ({ init, url }) => {
const moduleName = 'child_process'
const importStatement = `await import("${moduleName}")`
beforeEach(() => init(importStatement))
it('does not throw in dev at runtime', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expectNoError(moduleName)
})
it('does not throw in production at runtime', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expectNoError(moduleName)
})
})
describe.each([
{
title: 'Edge API',
@ -630,76 +371,3 @@ describe('Edge runtime code with imports', () => {
})
})
})
function getModuleNotFound(name) {
return `Module not found: Can't resolve '${name}'`
}
function getUnsupportedModule(name) {
return `The edge runtime does not support Node.js '${name}' module`
}
function getUnsupportedModuleWarning(name) {
return `A Node.js module is loaded ('${name}'`
}
function escapeLF(s) {
return s.replace(/\n/g, '\\n')
}
function expectUnsupportedModuleProdError(
moduleName,
output = context.logs.output
) {
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(output).toContain(moduleNotSupportedMessage)
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(output).not.toContain(moduleNotFoundMessage)
}
function expectUnsupportedModuleDevError(
moduleName,
importStatement,
responseText,
output = context.logs.output
) {
expectUnsupportedModuleProdError(moduleName, output)
expect(stripAnsi(output)).toContain(importStatement)
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(responseText).toContain(escapeLF(moduleNotSupportedMessage))
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(responseText).not.toContain(escapeLF(moduleNotFoundMessage))
}
function expectModuleNotFoundProdError(
moduleName,
output = context.logs.output
) {
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(stripAnsi(output)).not.toContain(moduleNotSupportedMessage)
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(stripAnsi(output)).toContain(moduleNotFoundMessage)
}
function expectModuleNotFoundDevError(
moduleName,
importStatement,
responseText,
output = context.logs.output
) {
expectModuleNotFoundProdError(moduleName, output)
expect(stripAnsi(output)).toContain(importStatement)
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(responseText).not.toContain(escapeLF(moduleNotSupportedMessage))
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(responseText).toContain(escapeLF(moduleNotFoundMessage))
}
function expectNoError(moduleName) {
expect(context.logs.output).not.toContain(getUnsupportedModule(moduleName))
expect(context.logs.output).not.toContain(getModuleNotFound(moduleName))
}

View file

@ -0,0 +1,294 @@
/* eslint-disable jest/no-identical-title */
/* eslint-env jest */
import { remove } from 'fs-extra'
import { join } from 'path'
import {
check,
fetchViaHTTP,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
} from 'next-test-utils'
import {
context,
appOption,
expectModuleNotFoundDevError,
expectModuleNotFoundProdError,
expectNoError,
expectUnsupportedModuleDevError,
expectUnsupportedModuleProdError,
getUnsupportedModuleWarning,
} from './utils'
jest.setTimeout(1000 * 60 * 2)
const routeUrl = '/api/route'
const middlewareUrl = '/'
describe('Edge runtime code with imports', () => {
beforeEach(async () => {
context.appPort = await findPort()
context.logs = { output: '', stdout: '', stderr: '' }
await remove(join(__dirname, '../.next'))
})
afterEach(async () => {
if (context.app) {
await killApp(context.app)
}
context.api.restore()
context.middleware.restore()
context.lib.restore()
context.page.restore()
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
${importStatement}
export default async function handler(request) {
basename()
return Response.json({ ok: basename() })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
${importStatement}
export async function middleware(request) {
basename()
return NextResponse.next()
}
`)
},
},
])('$title statically importing node.js module', ({ init, url }) => {
const moduleName = 'path'
const importStatement = `import { basename } from "${moduleName}"`
beforeEach(() => init(importStatement))
it('throws unsupported module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectUnsupportedModuleDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
it('throws unsupported module error in production at runtime and prints error on logs', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
expectUnsupportedModuleProdError(moduleName)
})
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
export default async function handler(request) {
new (${importStatement})()
return Response.json({ ok: true })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
export async function middleware(request) {
new (${importStatement})()
return NextResponse.next()
}
`)
},
},
])('$title dynamically importing 3rd party module', ({ init, url }) => {
const moduleName = 'not-exist'
const importStatement = `await import("${moduleName}")`
beforeEach(() => init(importStatement))
it('throws not-found module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectModuleNotFoundDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
export default async function handler(request) {
if (process.env === 'production') {
new (${importStatement})()
}
return Response.json({ ok: true })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
export async function middleware(request) {
if (process.env === 'production') {
new (${importStatement})()
}
return NextResponse.next()
}
`)
},
},
])('$title importing unused 3rd party module', ({ init, url }) => {
const moduleName = 'not-exist'
const importStatement = `await import("${moduleName}")`
beforeEach(() => init(importStatement))
it('throws not-found module error in dev at runtime and highlights the faulty line', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(500)
await check(async () => {
expectModuleNotFoundDevError(
moduleName,
importStatement,
await res.text()
)
return 'success'
}, 'success')
})
it('does not build and reports module not found error', async () => {
const { code, stderr } = await nextBuild(context.appDir, undefined, {
ignoreFail: true,
stdout: true,
stderr: true,
})
expect(code).toEqual(1)
expectModuleNotFoundProdError(moduleName, stderr)
})
})
describe.each([
{
title: 'Edge API',
url: routeUrl,
init(importStatement) {
context.api.write(`
export default async function handler(request) {
if (process.env === 'production') {
(${importStatement}).spawn('ls', ['-lh', '/usr'])
}
return Response.json({ ok: true })
}
export const config = { runtime: 'edge' }
`)
},
},
{
title: 'Middleware',
url: middlewareUrl,
init(importStatement) {
context.middleware.write(`
import { NextResponse } from 'next/server'
export async function middleware(request) {
if (process.env === 'production') {
(${importStatement}).spawn('ls', ['-lh', '/usr'])
}
return NextResponse.next()
}
`)
},
},
])('$title importing unused node.js module', ({ init, url }) => {
const moduleName = 'child_process'
const importStatement = `await import("${moduleName}")`
beforeEach(() => init(importStatement))
it('does not throw in dev at runtime', async () => {
context.app = await launchApp(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expectNoError(moduleName)
})
it('does not throw in production at runtime', async () => {
const { stderr } = await nextBuild(context.appDir, undefined, {
stderr: true,
})
expect(stderr).toContain(getUnsupportedModuleWarning(moduleName))
context.app = await nextStart(context.appDir, context.appPort, appOption)
const res = await fetchViaHTTP(context.appPort, url)
expect(res.status).toBe(200)
expectNoError(moduleName)
})
})
})

View file

@ -0,0 +1,96 @@
import { join } from 'path'
import stripAnsi from 'strip-ansi'
import { File } from 'next-test-utils'
export const context = {
appDir: join(__dirname, '../'),
logs: { output: '', stdout: '', stderr: '' },
api: new File(join(__dirname, '../pages/api/route.js')),
lib: new File(join(__dirname, '../lib.js')),
middleware: new File(join(__dirname, '../middleware.js')),
page: new File(join(__dirname, '../pages/index.js')),
}
export const appOption = {
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
onStdout(msg) {
context.logs.output += msg
context.logs.stdout += msg
},
onStderr(msg) {
context.logs.output += msg
context.logs.stderr += msg
},
}
export function getModuleNotFound(name) {
return `Module not found: Can't resolve '${name}'`
}
export function getUnsupportedModule(name) {
return `The edge runtime does not support Node.js '${name}' module`
}
export function getUnsupportedModuleWarning(name) {
return `A Node.js module is loaded ('${name}'`
}
export function escapeLF(s) {
return s.replace(/\n/g, '\\n')
}
export function expectUnsupportedModuleProdError(
moduleName,
output = context.logs.output
) {
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(output).toContain(moduleNotSupportedMessage)
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(output).not.toContain(moduleNotFoundMessage)
}
export function expectUnsupportedModuleDevError(
moduleName,
importStatement,
responseText,
output = context.logs.output
) {
expectUnsupportedModuleProdError(moduleName, output)
expect(stripAnsi(output)).toContain(importStatement)
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(responseText).toContain(escapeLF(moduleNotSupportedMessage))
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(responseText).not.toContain(escapeLF(moduleNotFoundMessage))
}
export function expectModuleNotFoundProdError(
moduleName,
output = context.logs.output
) {
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(stripAnsi(output)).not.toContain(moduleNotSupportedMessage)
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(stripAnsi(output)).toContain(moduleNotFoundMessage)
}
export function expectModuleNotFoundDevError(
moduleName,
importStatement,
responseText,
output = context.logs.output
) {
expectModuleNotFoundProdError(moduleName, output)
expect(stripAnsi(output)).toContain(importStatement)
const moduleNotSupportedMessage = getUnsupportedModule(moduleName)
expect(responseText).not.toContain(escapeLF(moduleNotSupportedMessage))
const moduleNotFoundMessage = getModuleNotFound(moduleName)
expect(responseText).toContain(escapeLF(moduleNotFoundMessage))
}
export function expectNoError(moduleName) {
expect(context.logs.output).not.toContain(getUnsupportedModule(moduleName))
expect(context.logs.output).not.toContain(getModuleNotFound(moduleName))
}

View file

@ -1,942 +0,0 @@
import fs from 'fs-extra'
import os from 'os'
import { join } from 'path'
import findUp from 'next/dist/compiled/find-up'
import { File, nextBuild, nextLint } from 'next-test-utils'
const dirFirstTimeSetup = join(__dirname, '../first-time-setup')
const dirCustomConfig = join(__dirname, '../custom-config')
const dirWebVitalsConfig = join(__dirname, '../config-core-web-vitals')
const dirPluginRecommendedConfig = join(
__dirname,
'../plugin-recommended-config'
)
const dirPluginCoreWebVitalsConfig = join(
__dirname,
'../plugin-core-web-vitals-config'
)
const dirIgnoreDuringBuilds = join(__dirname, '../ignore-during-builds')
const dirBaseDirectories = join(__dirname, '../base-directories')
const dirBaseDirectoriesConfigFile = new File(
join(dirBaseDirectories, '/next.config.js')
)
const dirCustomDirectories = join(__dirname, '../custom-directories')
const dirConfigInPackageJson = join(__dirname, '../config-in-package-json')
const dirInvalidOlderEslintVersion = join(
__dirname,
'../invalid-eslint-version'
)
const dirMaxWarnings = join(__dirname, '../max-warnings')
const dirEmptyDirectory = join(__dirname, '../empty-directory')
const dirEslintIgnore = join(__dirname, '../eslint-ignore')
const dirNoEslintPlugin = join(__dirname, '../no-eslint-plugin')
const dirNoConfig = join(__dirname, '../no-config')
const dirEslintCache = join(__dirname, '../eslint-cache')
const dirEslintCacheCustomDir = join(__dirname, '../eslint-cache-custom-dir')
const dirFileLinting = join(__dirname, '../file-linting')
const mjsCjsLinting = join(__dirname, '../mjs-cjs-linting')
const dirTypescript = join(__dirname, '../with-typescript')
describe('ESLint', () => {
describe('Next Build', () => {
test('first time setup', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '')
const { stdout, stderr } = await nextBuild(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'No ESLint configuration detected. Run next lint to begin setup'
)
})
test('shows warnings and errors', async () => {
const { stdout, stderr } = await nextBuild(dirCustomConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
test('ignore during builds', async () => {
const { stdout, stderr } = await nextBuild(dirIgnoreDuringBuilds, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Failed to compile')
expect(output).not.toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
// Consolidate two tests below when the `appDir` is released.
test('base directories are linted by default during builds', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: false,
}
}
`)
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain(
'Warning: Synchronous scripts should not be used'
)
expect(output).not.toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).not.toContain('app/layout.js')
})
test('base directories with appDir flag are linted by default during builds', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: true,
}
}
`)
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain(
'Warning: Synchronous scripts should not be used'
)
expect(output).toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, app, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).toContain('app/layout.js')
})
test('custom directories', async () => {
const { stdout, stderr } = await nextBuild(dirCustomDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('invalid older eslint version', async () => {
const { stdout, stderr } = await nextBuild(
dirInvalidOlderEslintVersion,
[],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain(
'Your project has an older version of ESLint installed'
)
})
test('empty directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEmptyDirectory, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('NoFilesFoundError')
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain('Compiled successfully')
})
test('eslint ignored directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEslintIgnore, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('AllFilesIgnoredError')
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain('Compiled successfully')
})
test('missing Next.js plugin', async () => {
const { stdout, stderr } = await nextBuild(dirNoEslintPlugin, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'The Next.js plugin was not detected in your ESLint configuration'
)
})
test('eslint caching is enabled', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextBuild(dirEslintCache, [])
const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')
await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)
await nextBuild(dirEslintCacheCustomDir, [])
expect(fs.existsSync(oldCacheDir)).toBe(false)
const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
})
describe('Next Lint', () => {
describe('First Time Setup ', () => {
async function nextLintTemp(setupCallback) {
const folder = join(
os.tmpdir(),
Math.random().toString(36).substring(2)
)
await fs.mkdirp(folder)
await fs.copy(dirNoConfig, folder)
await setupCallback?.(folder)
try {
const { stdout, stderr } = await nextLint(folder, ['--strict'], {
stderr: true,
stdout: true,
cwd: folder,
})
console.log({ stdout, stderr })
const pkgJson = JSON.parse(
await fs.readFile(join(folder, 'package.json'), 'utf8')
)
const eslintrcJson = JSON.parse(
await fs.readFile(join(folder, '.eslintrc.json'), 'utf8')
)
return { stdout, pkgJson, eslintrcJson }
} finally {
await fs.remove(folder)
}
}
test('show a prompt to set up ESLint if no configuration detected', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '')
const { stdout, stderr } = await nextLint(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('How would you like to configure ESLint?')
// Different options that can be selected
expect(output).toContain('Strict (recommended)')
expect(output).toContain('Base')
expect(output).toContain('Cancel')
})
for (const { packageManger, lockFile } of [
{ packageManger: 'yarn', lockFile: 'yarn.lock' },
{ packageManger: 'pnpm', lockFile: 'pnpm-lock.yaml' },
{ packageManger: 'npm', lockFile: 'package-lock.json' },
]) {
test(`installs eslint and eslint-config-next as devDependencies if missing with ${packageManger}`, async () => {
const { stdout, pkgJson } = await nextLintTemp(async (folder) => {
await fs.writeFile(join(folder, lockFile), '')
})
expect(stdout).toContain(
`Installing devDependencies (${packageManger}):`
)
expect(stdout).toContain('eslint')
expect(stdout).toContain('eslint-config-next')
expect(stdout).toContain(packageManger)
expect(pkgJson.devDependencies).toHaveProperty('eslint')
expect(pkgJson.devDependencies).toHaveProperty('eslint-config-next')
})
}
test('creates .eslintrc.json file with a default configuration', async () => {
const { stdout, eslintrcJson } = await nextLintTemp()
expect(stdout).toContain(
'We created the .eslintrc.json file for you and included your selected configuration'
)
expect(eslintrcJson).toMatchObject({ extends: 'next/core-web-vitals' })
})
test('shows a successful message when completed', async () => {
const { stdout } = await nextLintTemp()
expect(stdout).toContain(
'ESLint has successfully been configured. Run next lint again to view warnings and errors'
)
})
})
test('should generate next-env.d.ts before lint command', async () => {
await nextLint(dirTypescript, [], {
stdout: true,
stderr: true,
})
const files = await fs.readdir(dirTypescript)
expect(files).toContain('next-env.d.ts')
})
for (const { dir } of [
{ dir: dirEmptyDirectory },
{ dir: dirIgnoreDuringBuilds },
{ dir: dirCustomDirectories },
{ dir: dirConfigInPackageJson },
]) {
test('should not generate next-env.d.ts without typescript', async () => {
await nextLint(dir, [], {
stdout: true,
stderr: true,
})
const files = await fs.readdir(dir)
expect(files).not.toContain('next-env.d.ts')
})
}
test('should add relative path for dist types in tsconfig.json when app dir exist', async () => {
await nextLint(dirTypescript, [], {
stdout: true,
stderr: true,
})
const tsConfigPath = join(
dirTypescript,
'../with-typescript/tsconfig.json'
)
const tsConfigContent = await fs.readFile(tsConfigPath, {
encoding: 'utf8',
})
const tsConfigJson = JSON.parse(tsConfigContent)
expect(tsConfigJson.include).toContain('.build/types/**/*.ts')
})
test('shows warnings and errors', async () => {
const { stdout, stderr } = await nextLint(dirCustomConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
// Consolidate two tests below when the `appDir` is released.
test('base directories are linted by default', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: false,
}
}
`)
const { stdout, stderr } = await nextLint(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain(
'Warning: Synchronous scripts should not be used'
)
expect(output).not.toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).not.toContain('app/layout.js')
})
test('base directories with appDir flag are linted by default', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: true,
}
}
`)
const { stdout, stderr } = await nextLint(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain(
'Warning: Synchronous scripts should not be used'
)
expect(output).toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, app, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).toContain('app/layout.js')
})
test('shows warnings and errors with next/core-web-vitals config', async () => {
const { stdout, stderr } = await nextLint(dirWebVitalsConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Error: Synchronous scripts should not be used.')
})
test('shows warnings and errors when extending plugin recommended config', async () => {
const { stdout, stderr } = await nextLint(
dirPluginRecommendedConfig,
[],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(output).toContain(
'Error: `<Document />` from `next/document` should not be imported outside of `pages/_document.js`.'
)
})
test('shows warnings and errors when extending plugin core-web-vitals config', async () => {
const { stdout, stderr } = await nextLint(
dirPluginCoreWebVitalsConfig,
[],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Error: Synchronous scripts should not be used.')
})
test('success message when no warnings or errors', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '{ "extends": "next", "root": true }\n')
const { stdout, stderr } = await nextLint(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('No ESLint warnings or errors')
})
test("don't create .eslintrc file if package.json has eslintConfig field", async () => {
const eslintrcFile =
(await findUp(
[
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
'.eslintrc',
],
{
cwd: '.',
}
)) ?? null
try {
// If we found a .eslintrc file, it's probably config from root Next.js directory. Rename it during the test
if (eslintrcFile) {
await fs.move(eslintrcFile, `${eslintrcFile}.original`)
}
const { stdout, stderr } = await nextLint(dirConfigInPackageJson, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain(
'We created the .eslintrc file for you and included your selected configuration'
)
} finally {
// Restore original .eslintrc file
if (eslintrcFile) {
await fs.move(`${eslintrcFile}.original`, eslintrcFile)
}
}
})
test('quiet flag suppresses warnings and only reports errors', async () => {
const { stdout, stderr } = await nextLint(dirCustomConfig, ['--quiet'], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).not.toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('custom directories', async () => {
const { stdout, stderr } = await nextLint(dirCustomDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('max warnings flag errors when warnings exceed threshold', async () => {
const { stdout, stderr } = await nextLint(
dirMaxWarnings,
['--max-warnings', 1],
{
stdout: true,
stderr: true,
}
)
expect(stderr).not.toEqual('')
expect(stderr).toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(stdout).not.toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('max warnings flag does not error when warnings do not exceed threshold', async () => {
const { stdout, stderr } = await nextLint(
dirMaxWarnings,
['--max-warnings', 2],
{
stdout: true,
stderr: true,
}
)
expect(stderr).toEqual('')
expect(stderr).not.toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(stdout).toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('format flag supports additional user-defined formats', async () => {
const { stdout, stderr } = await nextLint(
dirMaxWarnings,
['-f', 'codeframe'],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain(
'warning: Synchronous scripts should not be used.'
)
expect(stdout).toContain('<script src="https://example.com" />')
expect(stdout).toContain('2 warnings found')
})
test('eslint caching is enabled by default', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache, [])
const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('eslint caching is disabled with the --no-cache flag', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--no-cache'])
expect(fs.existsSync(join(cacheDir, 'eslint/'))).toBe(false)
})
test('the default eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')
await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)
await nextLint(dirEslintCacheCustomDir, [])
expect(fs.existsSync(oldCacheDir)).toBe(false)
const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('the --cache-location flag allows the user to define a separate cache location', async () => {
const cacheFile = join(dirEslintCache, '.eslintcache')
await fs.remove(cacheFile)
await nextLint(dirEslintCache, ['--cache-location', cacheFile])
const hasCache = fs.existsSync(cacheFile)
await fs.remove(cacheFile) // remove after generate
expect(hasCache).toBe(true)
})
const getEslintCacheContent = async (cacheDir) => {
const eslintCacheDir = join(cacheDir, 'eslint/')
let files = await fs.readdir(eslintCacheDir)
let cacheFiles = files.filter((f) => /\.cache/.test(f))
expect(cacheFiles.length).toBe(1)
const cacheFile = join(eslintCacheDir, cacheFiles[0])
return await fs.readFile(cacheFile, 'utf8')
}
test('the default eslint caching strategy is metadata', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache)
const defaultStrategyCache = await getEslintCacheContent(cacheDir)
await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--cache-strategy', 'metadata'])
const metadataStrategyCache = await getEslintCacheContent(cacheDir)
expect(metadataStrategyCache).toBe(defaultStrategyCache)
})
test('cache with content strategy is different from the one with default strategy', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache)
const defaultStrategyCache = await getEslintCacheContent(cacheDir)
await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--cache-strategy', 'content'])
const contentStrategyCache = await getEslintCacheContent(cacheDir)
expect(contentStrategyCache).not.toBe(defaultStrategyCache)
})
test('file flag can selectively lint only a single file', async () => {
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--file', 'utils/math.js'],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain('utils/math.js')
expect(output).toContain(
'Comments inside children section of tag should be placed inside braces'
)
expect(output).not.toContain('pages/')
expect(output).not.toContain('Synchronous scripts should not be used.')
})
test('file flag can selectively lints multiple files', async () => {
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--file', 'utils/math.js', '--file', 'pages/bar.js'],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain('utils/math.js')
expect(output).toContain(
'Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain('pages/bar.js')
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).not.toContain('pages/index.js')
expect(output).not.toContain('Synchronous scripts should not be used.')
})
test('output flag create a file respecting the chosen format', async () => {
const filePath = `${__dirname}/output/output.json`
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--format', 'json', '--output-file', filePath],
{
stdout: true,
stderr: true,
}
)
const cliOutput = stdout + stderr
const fileOutput = await fs.readJSON(filePath)
expect(cliOutput).toContain(
`The output file has been created: ${filePath}`
)
if (fileOutput && fileOutput.length) {
fileOutput.forEach((file) => {
expect(file).toHaveProperty('filePath')
expect(file).toHaveProperty('messages')
expect(file).toHaveProperty('errorCount')
expect(file).toHaveProperty('warningCount')
expect(file).toHaveProperty('fixableErrorCount')
expect(file).toHaveProperty('fixableWarningCount')
expect(file).toHaveProperty('source')
expect(file).toHaveProperty('usedDeprecatedRules')
})
expect(fileOutput[0].messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
message:
'img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.',
}),
expect.objectContaining({
message:
'Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element',
}),
])
)
expect(fileOutput[1].messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
message:
'Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts',
}),
])
)
}
})
test('output flag create a file respecting the chosen format', async () => {
const filePath = `${__dirname}/output/output.txt`
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--format', 'compact', '--output-file', filePath],
{
stdout: true,
stderr: true,
}
)
const cliOutput = stdout + stderr
const fileOutput = fs.readFileSync(filePath, 'utf8')
expect(cliOutput).toContain(
`The output file has been created: ${filePath}`
)
expect(fileOutput).toContain('file-linting/pages/bar.js')
expect(fileOutput).toContain(
'img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.'
)
expect(fileOutput).toContain(
'Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element'
)
expect(fileOutput).toContain('file-linting/pages/index.js')
expect(fileOutput).toContain(
'Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts'
)
})
test('show error message when the file path is a directory', async () => {
const filePath = `${__dirname}`
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--format', 'compact', '--output-file', filePath],
{
stdout: true,
stderr: true,
}
)
const cliOutput = stdout + stderr
expect(cliOutput).toContain(
`Cannot write to output file path, it is a directory: ${filePath}`
)
})
test('lint files with cjs and mjs file extension', async () => {
const { stdout, stderr } = await nextLint(mjsCjsLinting, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('pages/bar.mjs')
expect(output).toContain(
'img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.'
)
expect(output).toContain(
'Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element'
)
expect(output).toContain('pages/index.cjs')
expect(output).toContain(
'Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts'
)
})
})
})

View file

@ -0,0 +1,130 @@
import fs from 'fs-extra'
import os from 'os'
import { join } from 'path'
import findUp from 'next/dist/compiled/find-up'
import { File, nextBuild, nextLint } from 'next-test-utils'
const dirFirstTimeSetup = join(__dirname, '../first-time-setup')
const dirCustomConfig = join(__dirname, '../custom-config')
const dirWebVitalsConfig = join(__dirname, '../config-core-web-vitals')
const dirPluginRecommendedConfig = join(
__dirname,
'../plugin-recommended-config'
)
const dirPluginCoreWebVitalsConfig = join(
__dirname,
'../plugin-core-web-vitals-config'
)
const dirIgnoreDuringBuilds = join(__dirname, '../ignore-during-builds')
const dirBaseDirectories = join(__dirname, '../base-directories')
const dirBaseDirectoriesConfigFile = new File(
join(dirBaseDirectories, '/next.config.js')
)
const dirCustomDirectories = join(__dirname, '../custom-directories')
const dirConfigInPackageJson = join(__dirname, '../config-in-package-json')
const dirInvalidOlderEslintVersion = join(
__dirname,
'../invalid-eslint-version'
)
const dirMaxWarnings = join(__dirname, '../max-warnings')
const dirEmptyDirectory = join(__dirname, '../empty-directory')
const dirEslintIgnore = join(__dirname, '../eslint-ignore')
const dirNoEslintPlugin = join(__dirname, '../no-eslint-plugin')
const dirNoConfig = join(__dirname, '../no-config')
const dirEslintCache = join(__dirname, '../eslint-cache')
const dirEslintCacheCustomDir = join(__dirname, '../eslint-cache-custom-dir')
const dirFileLinting = join(__dirname, '../file-linting')
const mjsCjsLinting = join(__dirname, '../mjs-cjs-linting')
const dirTypescript = join(__dirname, '../with-typescript')
test('eslint caching is enabled by default', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache, [])
const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('eslint caching is disabled with the --no-cache flag', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--no-cache'])
expect(fs.existsSync(join(cacheDir, 'eslint/'))).toBe(false)
})
test('the default eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')
await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)
await nextLint(dirEslintCacheCustomDir, [])
expect(fs.existsSync(oldCacheDir)).toBe(false)
const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('the --cache-location flag allows the user to define a separate cache location', async () => {
const cacheFile = join(dirEslintCache, '.eslintcache')
await fs.remove(cacheFile)
await nextLint(dirEslintCache, ['--cache-location', cacheFile])
const hasCache = fs.existsSync(cacheFile)
await fs.remove(cacheFile) // remove after generate
expect(hasCache).toBe(true)
})
const getEslintCacheContent = async (cacheDir) => {
const eslintCacheDir = join(cacheDir, 'eslint/')
let files = await fs.readdir(eslintCacheDir)
let cacheFiles = files.filter((f) => /\.cache/.test(f))
expect(cacheFiles.length).toBe(1)
const cacheFile = join(eslintCacheDir, cacheFiles[0])
return await fs.readFile(cacheFile, 'utf8')
}
test('the default eslint caching strategy is metadata', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache)
const defaultStrategyCache = await getEslintCacheContent(cacheDir)
await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--cache-strategy', 'metadata'])
const metadataStrategyCache = await getEslintCacheContent(cacheDir)
expect(metadataStrategyCache).toBe(defaultStrategyCache)
})
test('cache with content strategy is different from the one with default strategy', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextLint(dirEslintCache)
const defaultStrategyCache = await getEslintCacheContent(cacheDir)
await fs.remove(cacheDir)
await nextLint(dirEslintCache, ['--cache-strategy', 'content'])
const contentStrategyCache = await getEslintCacheContent(cacheDir)
expect(contentStrategyCache).not.toBe(defaultStrategyCache)
})

View file

@ -0,0 +1,253 @@
import fs from 'fs-extra'
import os from 'os'
import { join } from 'path'
import findUp from 'next/dist/compiled/find-up'
import { File, nextBuild, nextLint } from 'next-test-utils'
const dirFirstTimeSetup = join(__dirname, '../first-time-setup')
const dirCustomConfig = join(__dirname, '../custom-config')
const dirWebVitalsConfig = join(__dirname, '../config-core-web-vitals')
const dirPluginRecommendedConfig = join(
__dirname,
'../plugin-recommended-config'
)
const dirPluginCoreWebVitalsConfig = join(
__dirname,
'../plugin-core-web-vitals-config'
)
const dirIgnoreDuringBuilds = join(__dirname, '../ignore-during-builds')
const dirBaseDirectories = join(__dirname, '../base-directories')
const dirBaseDirectoriesConfigFile = new File(
join(dirBaseDirectories, '/next.config.js')
)
const dirCustomDirectories = join(__dirname, '../custom-directories')
const dirConfigInPackageJson = join(__dirname, '../config-in-package-json')
const dirInvalidOlderEslintVersion = join(
__dirname,
'../invalid-eslint-version'
)
const dirMaxWarnings = join(__dirname, '../max-warnings')
const dirEmptyDirectory = join(__dirname, '../empty-directory')
const dirEslintIgnore = join(__dirname, '../eslint-ignore')
const dirNoEslintPlugin = join(__dirname, '../no-eslint-plugin')
const dirNoConfig = join(__dirname, '../no-config')
const dirEslintCache = join(__dirname, '../eslint-cache')
const dirEslintCacheCustomDir = join(__dirname, '../eslint-cache-custom-dir')
const dirFileLinting = join(__dirname, '../file-linting')
const mjsCjsLinting = join(__dirname, '../mjs-cjs-linting')
const dirTypescript = join(__dirname, '../with-typescript')
describe('Next Build', () => {
test('first time setup', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '')
const { stdout, stderr } = await nextBuild(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'No ESLint configuration detected. Run next lint to begin setup'
)
})
test('shows warnings and errors', async () => {
const { stdout, stderr } = await nextBuild(dirCustomConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
test('ignore during builds', async () => {
const { stdout, stderr } = await nextBuild(dirIgnoreDuringBuilds, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Failed to compile')
expect(output).not.toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
// Consolidate two tests below when the `appDir` is released.
test('base directories are linted by default during builds', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: false,
}
}
`)
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain('Warning: Synchronous scripts should not be used')
expect(output).not.toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).not.toContain('app/layout.js')
})
test('base directories with appDir flag are linted by default during builds', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: true,
}
}
`)
const { stdout, stderr } = await nextBuild(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain('Warning: Synchronous scripts should not be used')
expect(output).toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, app, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).toContain('app/layout.js')
})
test('custom directories', async () => {
const { stdout, stderr } = await nextBuild(dirCustomDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Failed to compile')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain('Warning: Synchronous scripts should not be used.')
})
test('invalid older eslint version', async () => {
const { stdout, stderr } = await nextBuild(
dirInvalidOlderEslintVersion,
[],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain(
'Your project has an older version of ESLint installed'
)
})
test('empty directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEmptyDirectory, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('NoFilesFoundError')
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain('Compiled successfully')
})
test('eslint ignored directories do not fail the build', async () => {
const { stdout, stderr } = await nextBuild(dirEslintIgnore, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain('Build error occurred')
expect(output).not.toContain('AllFilesIgnoredError')
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain('Compiled successfully')
})
test('missing Next.js plugin', async () => {
const { stdout, stderr } = await nextBuild(dirNoEslintPlugin, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'The Next.js plugin was not detected in your ESLint configuration'
)
})
test('eslint caching is enabled', async () => {
const cacheDir = join(dirEslintCache, '.next', 'cache')
await fs.remove(cacheDir)
await nextBuild(dirEslintCache, [])
const files = await fs.readdir(join(cacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
test('eslint cache lives in the user defined build directory', async () => {
const oldCacheDir = join(dirEslintCacheCustomDir, '.next', 'cache')
const newCacheDir = join(dirEslintCacheCustomDir, 'build', 'cache')
await fs.remove(oldCacheDir)
await fs.remove(newCacheDir)
await nextBuild(dirEslintCacheCustomDir, [])
expect(fs.existsSync(oldCacheDir)).toBe(false)
const files = await fs.readdir(join(newCacheDir, 'eslint/'))
const cacheExists = files.some((f) => /\.cache/.test(f))
expect(cacheExists).toBe(true)
})
})

View file

@ -0,0 +1,595 @@
import fs from 'fs-extra'
import os from 'os'
import { join } from 'path'
import findUp from 'next/dist/compiled/find-up'
import { File, nextBuild, nextLint } from 'next-test-utils'
const dirFirstTimeSetup = join(__dirname, '../first-time-setup')
const dirCustomConfig = join(__dirname, '../custom-config')
const dirWebVitalsConfig = join(__dirname, '../config-core-web-vitals')
const dirPluginRecommendedConfig = join(
__dirname,
'../plugin-recommended-config'
)
const dirPluginCoreWebVitalsConfig = join(
__dirname,
'../plugin-core-web-vitals-config'
)
const dirIgnoreDuringBuilds = join(__dirname, '../ignore-during-builds')
const dirBaseDirectories = join(__dirname, '../base-directories')
const dirBaseDirectoriesConfigFile = new File(
join(dirBaseDirectories, '/next.config.js')
)
const dirCustomDirectories = join(__dirname, '../custom-directories')
const dirConfigInPackageJson = join(__dirname, '../config-in-package-json')
const dirInvalidOlderEslintVersion = join(
__dirname,
'../invalid-eslint-version'
)
const dirMaxWarnings = join(__dirname, '../max-warnings')
const dirEmptyDirectory = join(__dirname, '../empty-directory')
const dirEslintIgnore = join(__dirname, '../eslint-ignore')
const dirNoEslintPlugin = join(__dirname, '../no-eslint-plugin')
const dirNoConfig = join(__dirname, '../no-config')
const dirEslintCache = join(__dirname, '../eslint-cache')
const dirEslintCacheCustomDir = join(__dirname, '../eslint-cache-custom-dir')
const dirFileLinting = join(__dirname, '../file-linting')
const mjsCjsLinting = join(__dirname, '../mjs-cjs-linting')
const dirTypescript = join(__dirname, '../with-typescript')
describe('Next Lint', () => {
describe('First Time Setup ', () => {
async function nextLintTemp(setupCallback) {
const folder = join(os.tmpdir(), Math.random().toString(36).substring(2))
await fs.mkdirp(folder)
await fs.copy(dirNoConfig, folder)
await setupCallback?.(folder)
try {
const { stdout, stderr } = await nextLint(folder, ['--strict'], {
stderr: true,
stdout: true,
cwd: folder,
})
console.log({ stdout, stderr })
const pkgJson = JSON.parse(
await fs.readFile(join(folder, 'package.json'), 'utf8')
)
const eslintrcJson = JSON.parse(
await fs.readFile(join(folder, '.eslintrc.json'), 'utf8')
)
return { stdout, pkgJson, eslintrcJson }
} finally {
await fs.remove(folder)
}
}
test('show a prompt to set up ESLint if no configuration detected', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '')
const { stdout, stderr } = await nextLint(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('How would you like to configure ESLint?')
// Different options that can be selected
expect(output).toContain('Strict (recommended)')
expect(output).toContain('Base')
expect(output).toContain('Cancel')
})
for (const { packageManger, lockFile } of [
{ packageManger: 'yarn', lockFile: 'yarn.lock' },
{ packageManger: 'pnpm', lockFile: 'pnpm-lock.yaml' },
{ packageManger: 'npm', lockFile: 'package-lock.json' },
]) {
test(`installs eslint and eslint-config-next as devDependencies if missing with ${packageManger}`, async () => {
const { stdout, pkgJson } = await nextLintTemp(async (folder) => {
await fs.writeFile(join(folder, lockFile), '')
})
expect(stdout).toContain(
`Installing devDependencies (${packageManger}):`
)
expect(stdout).toContain('eslint')
expect(stdout).toContain('eslint-config-next')
expect(stdout).toContain(packageManger)
expect(pkgJson.devDependencies).toHaveProperty('eslint')
expect(pkgJson.devDependencies).toHaveProperty('eslint-config-next')
})
}
test('creates .eslintrc.json file with a default configuration', async () => {
const { stdout, eslintrcJson } = await nextLintTemp()
expect(stdout).toContain(
'We created the .eslintrc.json file for you and included your selected configuration'
)
expect(eslintrcJson).toMatchObject({ extends: 'next/core-web-vitals' })
})
test('shows a successful message when completed', async () => {
const { stdout } = await nextLintTemp()
expect(stdout).toContain(
'ESLint has successfully been configured. Run next lint again to view warnings and errors'
)
})
})
test('should generate next-env.d.ts before lint command', async () => {
await nextLint(dirTypescript, [], {
stdout: true,
stderr: true,
})
const files = await fs.readdir(dirTypescript)
expect(files).toContain('next-env.d.ts')
})
for (const { dir } of [
{ dir: dirEmptyDirectory },
{ dir: dirIgnoreDuringBuilds },
{ dir: dirCustomDirectories },
{ dir: dirConfigInPackageJson },
]) {
test('should not generate next-env.d.ts without typescript', async () => {
await nextLint(dir, [], {
stdout: true,
stderr: true,
})
const files = await fs.readdir(dir)
expect(files).not.toContain('next-env.d.ts')
})
}
test('should add relative path for dist types in tsconfig.json when app dir exist', async () => {
await nextLint(dirTypescript, [], {
stdout: true,
stderr: true,
})
const tsConfigPath = join(dirTypescript, '../with-typescript/tsconfig.json')
const tsConfigContent = await fs.readFile(tsConfigPath, {
encoding: 'utf8',
})
const tsConfigJson = JSON.parse(tsConfigContent)
expect(tsConfigJson.include).toContain('.build/types/**/*.ts')
})
test('shows warnings and errors', async () => {
const { stdout, stderr } = await nextLint(dirCustomConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
})
// Consolidate two tests below when the `appDir` is released.
test('base directories are linted by default', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: false,
}
}
`)
const { stdout, stderr } = await nextLint(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain('Warning: Synchronous scripts should not be used')
expect(output).not.toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).not.toContain('app/layout.js')
})
test('base directories with appDir flag are linted by default', async () => {
dirBaseDirectoriesConfigFile.write(`
module.exports = {
experimental: {
appDir: true,
}
}
`)
const { stdout, stderr } = await nextLint(dirBaseDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: `next/head` should not be imported in `pages/_document.js`. Use `<Head />` from `next/document` instead'
)
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Warning: Do not include stylesheets manually')
expect(output).toContain('Warning: Synchronous scripts should not be used')
expect(output).toContain(
'Warning: `rel="preconnect"` is missing from Google Font'
)
// Files in pages, app, components, lib, and src directories are linted
expect(output).toContain('pages/_document.js')
expect(output).toContain('components/bar.js')
expect(output).toContain('lib/foo.js')
expect(output).toContain('src/index.js')
expect(output).toContain('app/layout.js')
})
test('shows warnings and errors with next/core-web-vitals config', async () => {
const { stdout, stderr } = await nextLint(dirWebVitalsConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Error: Synchronous scripts should not be used.')
})
test('shows warnings and errors when extending plugin recommended config', async () => {
const { stdout, stderr } = await nextLint(dirPluginRecommendedConfig, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('Warning: Synchronous scripts should not be used.')
expect(output).toContain(
'Error: `<Document />` from `next/document` should not be imported outside of `pages/_document.js`.'
)
})
test('shows warnings and errors when extending plugin core-web-vitals config', async () => {
const { stdout, stderr } = await nextLint(
dirPluginCoreWebVitalsConfig,
[],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).toContain('Error: Synchronous scripts should not be used.')
})
test('success message when no warnings or errors', async () => {
const eslintrcJson = join(dirFirstTimeSetup, '.eslintrc.json')
await fs.writeFile(eslintrcJson, '{ "extends": "next", "root": true }\n')
const { stdout, stderr } = await nextLint(dirFirstTimeSetup, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('No ESLint warnings or errors')
})
test("don't create .eslintrc file if package.json has eslintConfig field", async () => {
const eslintrcFile =
(await findUp(
[
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc.json',
'.eslintrc',
],
{
cwd: '.',
}
)) ?? null
try {
// If we found a .eslintrc file, it's probably config from root Next.js directory. Rename it during the test
if (eslintrcFile) {
await fs.move(eslintrcFile, `${eslintrcFile}.original`)
}
const { stdout, stderr } = await nextLint(dirConfigInPackageJson, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).not.toContain(
'We created the .eslintrc file for you and included your selected configuration'
)
} finally {
// Restore original .eslintrc file
if (eslintrcFile) {
await fs.move(`${eslintrcFile}.original`, eslintrcFile)
}
}
})
test('quiet flag suppresses warnings and only reports errors', async () => {
const { stdout, stderr } = await nextLint(dirCustomConfig, ['--quiet'], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).not.toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('custom directories', async () => {
const { stdout, stderr } = await nextLint(dirCustomDirectories, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain(
'Error: Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain('Warning: Synchronous scripts should not be used.')
})
test('max warnings flag errors when warnings exceed threshold', async () => {
const { stdout, stderr } = await nextLint(
dirMaxWarnings,
['--max-warnings', 1],
{
stdout: true,
stderr: true,
}
)
expect(stderr).not.toEqual('')
expect(stderr).toContain('Warning: Synchronous scripts should not be used.')
expect(stdout).not.toContain(
'Warning: Synchronous scripts should not be used.'
)
})
test('max warnings flag does not error when warnings do not exceed threshold', async () => {
const { stdout, stderr } = await nextLint(
dirMaxWarnings,
['--max-warnings', 2],
{
stdout: true,
stderr: true,
}
)
expect(stderr).toEqual('')
expect(stderr).not.toContain(
'Warning: Synchronous scripts should not be used.'
)
expect(stdout).toContain('Warning: Synchronous scripts should not be used.')
})
test('format flag supports additional user-defined formats', async () => {
const { stdout, stderr } = await nextLint(
dirMaxWarnings,
['-f', 'codeframe'],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain('warning: Synchronous scripts should not be used.')
expect(stdout).toContain('<script src="https://example.com" />')
expect(stdout).toContain('2 warnings found')
})
test('file flag can selectively lint only a single file', async () => {
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--file', 'utils/math.js'],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain('utils/math.js')
expect(output).toContain(
'Comments inside children section of tag should be placed inside braces'
)
expect(output).not.toContain('pages/')
expect(output).not.toContain('Synchronous scripts should not be used.')
})
test('file flag can selectively lints multiple files', async () => {
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--file', 'utils/math.js', '--file', 'pages/bar.js'],
{
stdout: true,
stderr: true,
}
)
const output = stdout + stderr
expect(output).toContain('utils/math.js')
expect(output).toContain(
'Comments inside children section of tag should be placed inside braces'
)
expect(output).toContain('pages/bar.js')
expect(output).toContain(
'Warning: Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images.'
)
expect(output).not.toContain('pages/index.js')
expect(output).not.toContain('Synchronous scripts should not be used.')
})
test('output flag create a file respecting the chosen format', async () => {
const filePath = `${__dirname}/output/output.json`
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--format', 'json', '--output-file', filePath],
{
stdout: true,
stderr: true,
}
)
const cliOutput = stdout + stderr
const fileOutput = await fs.readJSON(filePath)
expect(cliOutput).toContain(`The output file has been created: ${filePath}`)
if (fileOutput && fileOutput.length) {
fileOutput.forEach((file) => {
expect(file).toHaveProperty('filePath')
expect(file).toHaveProperty('messages')
expect(file).toHaveProperty('errorCount')
expect(file).toHaveProperty('warningCount')
expect(file).toHaveProperty('fixableErrorCount')
expect(file).toHaveProperty('fixableWarningCount')
expect(file).toHaveProperty('source')
expect(file).toHaveProperty('usedDeprecatedRules')
})
expect(fileOutput[0].messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
message:
'img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.',
}),
expect.objectContaining({
message:
'Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element',
}),
])
)
expect(fileOutput[1].messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
message:
'Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts',
}),
])
)
}
})
test('output flag create a file respecting the chosen format', async () => {
const filePath = `${__dirname}/output/output.txt`
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--format', 'compact', '--output-file', filePath],
{
stdout: true,
stderr: true,
}
)
const cliOutput = stdout + stderr
const fileOutput = fs.readFileSync(filePath, 'utf8')
expect(cliOutput).toContain(`The output file has been created: ${filePath}`)
expect(fileOutput).toContain('file-linting/pages/bar.js')
expect(fileOutput).toContain(
'img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.'
)
expect(fileOutput).toContain(
'Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element'
)
expect(fileOutput).toContain('file-linting/pages/index.js')
expect(fileOutput).toContain(
'Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts'
)
})
test('show error message when the file path is a directory', async () => {
const filePath = `${__dirname}`
const { stdout, stderr } = await nextLint(
dirFileLinting,
['--format', 'compact', '--output-file', filePath],
{
stdout: true,
stderr: true,
}
)
const cliOutput = stdout + stderr
expect(cliOutput).toContain(
`Cannot write to output file path, it is a directory: ${filePath}`
)
})
test('lint files with cjs and mjs file extension', async () => {
const { stdout, stderr } = await nextLint(mjsCjsLinting, [], {
stdout: true,
stderr: true,
})
const output = stdout + stderr
expect(output).toContain('pages/bar.mjs')
expect(output).toContain(
'img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.'
)
expect(output).toContain(
'Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element'
)
expect(output).toContain('pages/index.cjs')
expect(output).toContain(
'Synchronous scripts should not be used. See: https://nextjs.org/docs/messages/no-sync-scripts'
)
})
})

View file

@ -229,7 +229,9 @@ describe('SSG Prerender', () => {
try {
await fs.remove(nextConfigPath)
await killApp(app)
} catch (_) {}
} catch (err) {
console.error(err)
}
})
it('should work with firebase import and getStaticPaths', async () => {
@ -339,6 +341,7 @@ describe('SSG Prerender', () => {
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
})
afterAll(async () => {
try {
await fs.remove(nextConfigPath)
await stopApp(app)
@ -356,6 +359,9 @@ describe('SSG Prerender', () => {
const pagePath = join(appDir, 'pages', page)
await fs.rename(`${pagePath}.bak`, pagePath)
}
} catch (err) {
console.error(err)
}
})
it('should copy prerender files and honor exportTrailingSlash', async () => {

View file

@ -233,154 +233,6 @@ describe('Can hot reload CSS Module without losing state', () => {
})
})
describe.skip('Invalid CSS Module Usage in node_modules', () => {
const appDir = join(fixturesDir, 'invalid-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.module.scss')
expect(stderr).toMatch(
/CSS Modules.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
})
})
describe.skip('Invalid CSS Global Module Usage in node_modules', () => {
const appDir = join(fixturesDir, 'invalid-global-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.scss')
expect(stderr).toMatch(
/Global CSS.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
})
})
describe('Valid CSS Module Usage from within node_modules', () => {
const appDir = join(fixturesDir, 'nm-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've prerendered with relevant data`, async () => {
const content = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(content)
const cssPreload = $('#nm-div')
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{\\"message\\":\\"Why hello there\\"} {\\"redText\\":\\"example_redText__8_1ap\\"}"`
)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".example_redText__8_1ap{color:red}"`
)
})
})
describe('Valid Nested CSS Module Usage from within node_modules', () => {
const appDir = join(fixturesDir, 'nm-module-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've prerendered with relevant data`, async () => {
const content = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(content)
const cssPreload = $('#nm-div')
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{\\"message\\":\\"Why hello there\\"} {\\"other2\\":\\"example_other2__pD1TP\\",\\"subClass\\":\\"example_subClass___Qywg other_className__jR2X2\\"}"`
)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".other_other3__QRKUk{color:violet}.other_className__jR2X2{background:red;color:#ff0}.example_other2__pD1TP{color:red}.example_subClass___Qywg{background:blue}"`
)
})
})
describe('CSS Module Composes Usage (Basic)', () => {
// This is a very bad feature. Do not use it.
const appDir = join(fixturesDir, 'composes-basic')
@ -446,99 +298,3 @@ describe('CSS Module Composes Usage (External)', () => {
)
})
})
describe('Dynamic Route CSS Module Usage', () => {
const appDir = join(fixturesDir, 'dynamic-route-module')
let stdout
let code
let app
let appPort
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"._post__home__a9vTy{background:red}"`
)
})
it('should apply styles correctly', async () => {
const browser = await webdriver(appPort, '/post-1')
const background = await browser
.elementByCss('#my-div')
.getComputedCss('background-color')
expect(background).toMatch(/rgb(a|)\(255, 0, 0/)
})
})
describe('Catch-all Route CSS Module Usage', () => {
const appDir = join(fixturesDir, 'catch-all-module')
let stdout
let code
let app
let appPort
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should apply styles correctly', async () => {
const browser = await webdriver(appPort, '/post-1')
const background = await browser
.elementByCss('#my-div')
.getComputedCss('background-color')
expect(background).toMatch(/rgb(a|)\(255, 0, 0/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".___post__home__w1yuY{background:red}"`
)
})
})

View file

@ -0,0 +1,104 @@
/* eslint-env jest */
import { readdir, readFile, remove } from 'fs-extra'
import { findPort, killApp, nextBuild, nextStart } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../../scss-fixtures')
describe('Dynamic Route CSS Module Usage', () => {
const appDir = join(fixturesDir, 'dynamic-route-module')
let stdout
let code
let app
let appPort
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`"._post__home__a9vTy{background:red}"`
)
})
it('should apply styles correctly', async () => {
const browser = await webdriver(appPort, '/post-1')
const background = await browser
.elementByCss('#my-div')
.getComputedCss('background-color')
expect(background).toMatch(/rgb(a|)\(255, 0, 0/)
})
})
describe('Catch-all Route CSS Module Usage', () => {
const appDir = join(fixturesDir, 'catch-all-module')
let stdout
let code
let app
let appPort
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should apply styles correctly', async () => {
const browser = await webdriver(appPort, '/post-1')
const background = await browser
.elementByCss('#my-div')
.getComputedCss('background-color')
expect(background).toMatch(/rgb(a|)\(255, 0, 0/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".___post__home__w1yuY{background:red}"`
)
})
})

View file

@ -0,0 +1,162 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import { readdir, readFile, remove } from 'fs-extra'
import {
findPort,
killApp,
nextBuild,
nextStart,
renderViaHTTP,
} from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../../scss-fixtures')
describe.skip('Invalid CSS Module Usage in node_modules', () => {
const appDir = join(fixturesDir, 'invalid-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.module.scss')
expect(stderr).toMatch(
/CSS Modules.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
})
})
describe.skip('Invalid CSS Global Module Usage in node_modules', () => {
const appDir = join(fixturesDir, 'invalid-global-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('node_modules/example/index.scss')
expect(stderr).toMatch(
/Global CSS.*cannot.*be imported from within.*node_modules/
)
expect(stderr).toMatch(/Location:.*node_modules[\\/]example[\\/]index\.mjs/)
})
})
describe('Valid CSS Module Usage from within node_modules', () => {
const appDir = join(fixturesDir, 'nm-module')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've prerendered with relevant data`, async () => {
const content = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(content)
const cssPreload = $('#nm-div')
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{\\"message\\":\\"Why hello there\\"} {\\"redText\\":\\"example_redText__8_1ap\\"}"`
)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".example_redText__8_1ap{color:red}"`
)
})
})
describe('Valid Nested CSS Module Usage from within node_modules', () => {
const appDir = join(fixturesDir, 'nm-module-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've prerendered with relevant data`, async () => {
const content = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(content)
const cssPreload = $('#nm-div')
expect(cssPreload.text()).toMatchInlineSnapshot(
`"{\\"message\\":\\"Why hello there\\"} {\\"other2\\":\\"example_other2__pD1TP\\",\\"subClass\\":\\"example_subClass___Qywg other_className__jR2X2\\"}"`
)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".other_other3__QRKUk{color:violet}.other_className__jR2X2{background:red;color:#ff0}.example_other2__pD1TP{color:red}.example_subClass___Qywg{background:blue}"`
)
})
})

View file

@ -0,0 +1,280 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
describe('Basic Global Support', () => {
const appDir = join(fixturesDir, 'single-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Module Include Paths Support', () => {
const appDir = join(fixturesDir, 'basic-module-include-paths')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Module Prepend Data Support', () => {
const appDir = join(fixturesDir, 'basic-module-prepend-data')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Global Support with src/ dir', () => {
const appDir = join(fixturesDir, 'single-global-src')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Multi Global Support', () => {
const appDir = join(fixturesDir, 'multi-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".red-text{color:red}.blue-text{color:blue}"`
)
})
})
describe('Nested @import() Global Support', () => {
const appDir = join(fixturesDir, 'nested-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".red-text{color:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:blue}"`
)
})
})
// Tests css ordering
describe('Multi Global Support (reversed)', () => {
const appDir = join(fixturesDir, 'multi-global-reversed')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".blue-text{color:blue}.red-text{color:red}"`
)
})
})
describe('Good CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(/nprogress/)
})
})
describe('Good Nested CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot(
`".other{color:blue}.test{color:red}"`
)
})
})
describe('CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-bad')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail the build', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(0)
expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
expect(stderr).not.toMatch(/Build error occurred/)
})
})

View file

@ -0,0 +1,231 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import {
File,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
import { quote as shellQuote } from 'shell-quote'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
describe('SCSS Support', () => {
describe('Friendly Webpack Error', () => {
const appDir = join(fixturesDir, 'webpack-error')
const mockFile = join(appDir, 'mock.js')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should be a friendly error successfully', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
env: { NODE_OPTIONS: shellQuote([`--require`, mockFile]) },
stderr: true,
})
let cleanScssErrMsg =
'\n\n' +
'./styles/global.scss\n' +
"To use Next.js' built-in Sass support, you first need to install `sass`.\n" +
'Run `npm i sass` or `yarn add sass` inside your workspace.\n' +
'\n' +
'Learn more: https://nextjs.org/docs/messages/install-sass\n'
expect(code).toBe(1)
expect(stderr).toContain('Failed to compile.')
expect(stderr).toContain(cleanScssErrMsg)
expect(stderr).not.toContain('css-loader')
expect(stderr).not.toContain('sass-loader')
})
})
describe('CSS Compilation and Prefixing', () => {
const appDir = join(fixturesDir, 'compilation-and-prefixing')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've compiled and prefixed`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".redText ::placeholder{color:red}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}"`
)
// Contains a source map
expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
})
it(`should've emitted a source map`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssMapFiles = files.filter((f) => /\.css\.map$/.test(f))
expect(cssMapFiles.length).toBe(1)
const cssMapContent = (
await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
).trim()
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
Object {
"mappings": "AAEE,uBACE,SAHE,CAON,cACE,2CAAA",
"sourcesContent": Array [
"$var: red;
.redText {
::placeholder {
color: $var;
}
}
.flex-parsing {
flex: 0 0 calc(50% - var(--vertical-gutter));
}
",
],
"version": 3,
}
`)
})
})
describe('Can hot reload CSS without losing state', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should update CSS color without remounting <input>', async () => {
let browser
try {
browser = await webdriver(appPort, '/page1')
const desiredText = 'hello world'
await browser.elementById('text-input').type(desiredText)
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
const cssFile = new File(join(appDir, 'styles/global1.scss'))
try {
cssFile.replace('$var: red', '$var: purple')
await waitFor(2000) // wait for HMR
const refreshedColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
)
expect(refreshedColor).toMatchInlineSnapshot(`"rgb(128, 0, 128)"`)
// ensure text remained
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
} finally {
cssFile.restore()
}
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Production', () => {
const appDir = join(fixturesDir, 'multi-page')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have CSS for page', async () => {
const browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it(`should've preloaded the CSS file and injected it in <head>`, async () => {
const content = await renderViaHTTP(appPort, '/page2')
const $ = cheerio.load(content)
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
/* ensure CSS preloaded first */
const allPreloads = [].slice.call($('link[rel="preload"]'))
const styleIndexes = allPreloads.flatMap((p, i) =>
p.attribs.as === 'style' ? i : []
)
expect(styleIndexes).toEqual([0])
})
})
})

View file

@ -0,0 +1,73 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { remove } from 'fs-extra'
import {
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
describe('Ordering with styled-jsx (dev)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
describe('Ordering with styled-jsx (prod)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})

View file

@ -0,0 +1,111 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { remove } from 'fs-extra'
import { File, findPort, killApp, launchApp, waitFor } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
describe('Has CSS in computed styles in Development', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have CSS for page', async () => {
let browser
try {
browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when unused in Development', () => {
const appDir = join(fixturesDir, 'unused')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when broken in Development', () => {
const appDir = join(fixturesDir, 'unused')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
const pageFile = new File(join(appDir, 'pages/index.js'))
let browser
try {
pageFile.replace('<div />', '<div>')
await waitFor(2000) // wait for recompile
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
pageFile.restore()
if (browser) {
await browser.close()
}
}
})
})

View file

@ -1,617 +0,0 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import {
File,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
import { quote as shellQuote } from 'shell-quote'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
describe('SCSS Support', () => {
describe('Friendly Webpack Error', () => {
const appDir = join(fixturesDir, 'webpack-error')
const mockFile = join(appDir, 'mock.js')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should be a friendly error successfully', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
env: { NODE_OPTIONS: shellQuote([`--require`, mockFile]) },
stderr: true,
})
let cleanScssErrMsg =
'\n\n' +
'./styles/global.scss\n' +
"To use Next.js' built-in Sass support, you first need to install `sass`.\n" +
'Run `npm i sass` or `yarn add sass` inside your workspace.\n' +
'\n' +
'Learn more: https://nextjs.org/docs/messages/install-sass\n'
expect(code).toBe(1)
expect(stderr).toContain('Failed to compile.')
expect(stderr).toContain(cleanScssErrMsg)
expect(stderr).not.toContain('css-loader')
expect(stderr).not.toContain('sass-loader')
})
})
describe('Basic Global Support', () => {
const appDir = join(fixturesDir, 'single-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Module Include Paths Support', () => {
const appDir = join(fixturesDir, 'basic-module-include-paths')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Module Prepend Data Support', () => {
const appDir = join(fixturesDir, 'basic-module-prepend-data')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Basic Global Support with src/ dir', () => {
const appDir = join(fixturesDir, 'single-global-src')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
expect(await readFile(join(cssFolder, cssFiles[0]), 'utf8')).toContain(
'color:red'
)
})
})
describe('Multi Global Support', () => {
const appDir = join(fixturesDir, 'multi-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".red-text{color:red}.blue-text{color:blue}"`)
})
})
describe('Nested @import() Global Support', () => {
const appDir = join(fixturesDir, 'nested-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".red-text{color:purple;font-weight:bolder;color:red}.blue-text{color:orange;font-weight:bolder;color:blue}"`
)
})
})
describe('CSS Compilation and Prefixing', () => {
const appDir = join(fixturesDir, 'compilation-and-prefixing')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've compiled and prefixed`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(
`".redText ::placeholder{color:red}.flex-parsing{flex:0 0 calc(50% - var(--vertical-gutter))}"`
)
// Contains a source map
expect(cssContent).toMatch(/\/\*#\s*sourceMappingURL=(.+\.map)\s*\*\//)
})
it(`should've emitted a source map`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssMapFiles = files.filter((f) => /\.css\.map$/.test(f))
expect(cssMapFiles.length).toBe(1)
const cssMapContent = (
await readFile(join(cssFolder, cssMapFiles[0]), 'utf8')
).trim()
const { version, mappings, sourcesContent } = JSON.parse(cssMapContent)
expect({ version, mappings, sourcesContent }).toMatchInlineSnapshot(`
Object {
"mappings": "AAEE,uBACE,SAHE,CAON,cACE,2CAAA",
"sourcesContent": Array [
"$var: red;
.redText {
::placeholder {
color: $var;
}
}
.flex-parsing {
flex: 0 0 calc(50% - var(--vertical-gutter));
}
",
],
"version": 3,
}
`)
})
})
// Tests css ordering
describe('Multi Global Support (reversed)', () => {
const appDir = join(fixturesDir, 'multi-global-reversed')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".blue-text{color:blue}.red-text{color:red}"`)
})
})
describe('Invalid CSS in _document', () => {
const appDir = join(fixturesDir, 'invalid-module-document')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles.module.scss')
expect(stderr).toMatch(
/CSS.*cannot.*be imported within.*pages[\\/]_document\.js/
)
expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
})
})
describe('Invalid Global CSS', () => {
const appDir = join(fixturesDir, 'invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.scss')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'invalid-global-with-app')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.scss')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Valid and Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'valid-and-invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.scss')
expect(stderr).toContain('Please move all first-party global CSS imports')
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Can hot reload CSS without losing state', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should update CSS color without remounting <input>', async () => {
let browser
try {
browser = await webdriver(appPort, '/page1')
const desiredText = 'hello world'
await browser.elementById('text-input').type(desiredText)
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(255, 0, 0)"`)
const cssFile = new File(join(appDir, 'styles/global1.scss'))
try {
cssFile.replace('$var: red', '$var: purple')
await waitFor(2000) // wait for HMR
const refreshedColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.red-text')).color`
)
expect(refreshedColor).toMatchInlineSnapshot(`"rgb(128, 0, 128)"`)
// ensure text remained
expect(await browser.elementById('text-input').getValue()).toBe(
desiredText
)
} finally {
cssFile.restore()
}
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Development', () => {
const appDir = join(fixturesDir, 'multi-page')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have CSS for page', async () => {
let browser
try {
browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when unused in Development', () => {
const appDir = join(fixturesDir, 'unused')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
let appPort
let app
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
let browser
try {
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
if (browser) {
await browser.close()
}
}
})
})
describe('Body is not hidden when broken in Development', () => {
const appDir = join(fixturesDir, 'unused')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have body visible', async () => {
const pageFile = new File(join(appDir, 'pages/index.js'))
let browser
try {
pageFile.replace('<div />', '<div>')
await waitFor(2000) // wait for recompile
browser = await webdriver(appPort, '/')
const currentDisplay = await browser.eval(
`window.getComputedStyle(document.querySelector('body')).display`
)
expect(currentDisplay).toBe('block')
} finally {
pageFile.restore()
if (browser) {
await browser.close()
}
}
})
})
describe('Has CSS in computed styles in Production', () => {
const appDir = join(fixturesDir, 'multi-page')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have CSS for page', async () => {
const browser = await webdriver(appPort, '/page2')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.blue-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 0, 255)"`)
})
it(`should've preloaded the CSS file and injected it in <head>`, async () => {
const content = await renderViaHTTP(appPort, '/page2')
const $ = cheerio.load(content)
const cssPreload = $('link[rel="preload"][as="style"]')
expect(cssPreload.length).toBe(1)
expect(cssPreload.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
const cssSheet = $('link[rel="stylesheet"]')
expect(cssSheet.length).toBe(1)
expect(cssSheet.attr('href')).toMatch(/^\/_next\/static\/css\/.*\.css$/)
/* ensure CSS preloaded first */
const allPreloads = [].slice.call($('link[rel="preload"]'))
const styleIndexes = allPreloads.flatMap((p, i) =>
p.attribs.as === 'style' ? i : []
)
expect(styleIndexes).toEqual([0])
})
})
})

View file

@ -2,14 +2,7 @@
import 'flat-map-polyfill'
import { readdir, readFile, remove } from 'fs-extra'
import {
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
@ -259,78 +252,6 @@ describe('SCSS Support', () => {
})
})
describe('Good CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch(/nprogress/)
})
})
describe('Good Nested CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-nested')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it(`should've emitted a single CSS file`, async () => {
const cssFolder = join(appDir, '.next/static/css')
const files = await readdir(cssFolder)
const cssFiles = files.filter((f) => /\.css$/.test(f))
expect(cssFiles.length).toBe(1)
const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8')
expect(
cssContent.replace(/\/\*.*?\*\//g, '').trim()
).toMatchInlineSnapshot(`".other{color:blue}.test{color:red}"`)
})
})
describe('CSS Import from node_modules', () => {
const appDir = join(fixturesDir, 'npm-import-bad')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail the build', async () => {
const { code, stderr } = await nextBuild(appDir, [], { stderr: true })
expect(code).toBe(0)
expect(stderr).not.toMatch(/Can't resolve '[^']*?nprogress[^']*?'/)
expect(stderr).not.toMatch(/Build error occurred/)
})
})
describe('Preprocessor loader order', () => {
const appDir = join(fixturesDir, 'loader-order')
@ -345,62 +266,4 @@ describe('SCSS Support', () => {
expect(stdout).toMatch(/Compiled successfully/)
})
})
describe('Ordering with styled-jsx (dev)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
beforeAll(async () => {
await remove(join(appDir, '.next'))
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
describe('Ordering with styled-jsx (prod)', () => {
const appDir = join(fixturesDir, 'with-styled-jsx')
let appPort
let app
let stdout
let code
beforeAll(async () => {
await remove(join(appDir, '.next'))
;({ code, stdout } = await nextBuild(appDir, [], {
stdout: true,
}))
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
})
it('should have compiled successfully', () => {
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
it('should have the correct color (css ordering)', async () => {
const browser = await webdriver(appPort, '/')
const currentColor = await browser.eval(
`window.getComputedStyle(document.querySelector('.my-text')).color`
)
expect(currentColor).toMatchInlineSnapshot(`"rgb(0, 128, 0)"`)
})
})
})

View file

@ -0,0 +1,90 @@
/* eslint-env jest */
import 'flat-map-polyfill'
import { remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
const fixturesDir = join(__dirname, '../..', 'scss-fixtures')
describe('Invalid CSS in _document', () => {
const appDir = join(fixturesDir, 'invalid-module-document')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles.module.scss')
expect(stderr).toMatch(
/CSS.*cannot.*be imported within.*pages[\\/]_document\.js/
)
expect(stderr).toMatch(/Location:.*pages[\\/]_document\.js/)
})
})
describe('Invalid Global CSS', () => {
const appDir = join(fixturesDir, 'invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.scss')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'invalid-global-with-app')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.scss')
expect(stderr).toMatch(
/Please move all first-party global CSS imports.*?pages(\/|\\)_app/
)
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})
describe('Valid and Invalid Global CSS with Custom App', () => {
const appDir = join(fixturesDir, 'valid-and-invalid-global')
beforeAll(async () => {
await remove(join(appDir, '.next'))
})
it('should fail to build', async () => {
const { code, stderr } = await nextBuild(appDir, [], {
stderr: true,
})
expect(code).not.toBe(0)
expect(stderr).toContain('Failed to compile')
expect(stderr).toContain('styles/global.scss')
expect(stderr).toContain('Please move all first-party global CSS imports')
expect(stderr).toMatch(/Location:.*pages[\\/]index\.js/)
})
})

View file

@ -29,11 +29,18 @@ describe('config telemetry', () => {
path.join(appDir, 'next.config.custom-routes')
)
const event1 = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/.exec(stderr).pop()
try {
const event1 = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()
expect(event1).toMatch(/"headersCount": 1/)
expect(event1).toMatch(/"rewritesCount": 2/)
expect(event1).toMatch(/"redirectsCount": 1/)
expect(event1).toMatch(/"middlewareCount": 0/)
} catch (err) {
require('console').error(stderr)
throw err
}
})
it('detects i18n and image configs for session start', async () => {
@ -52,6 +59,7 @@ describe('config telemetry', () => {
path.join(appDir, 'next.config.i18n-images')
)
try {
const event1 = /NEXT_CLI_SESSION_STARTED[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()
@ -72,6 +80,10 @@ describe('config telemetry', () => {
expect(event1).toMatch(/"turboFlag": false/)
expect(event1).toMatch(/"pagesDir": true/)
expect(event1).toMatch(/"appDir": false/)
} catch (err) {
require('console').error(stderr)
throw err
}
await fs.rename(
path.join(appDir, 'next.config.i18n-images'),
@ -96,6 +108,7 @@ describe('config telemetry', () => {
path.join(appDir, 'next.config.i18n-images')
)
try {
const event2 = /NEXT_CLI_SESSION_STARTED[\s\S]+?{([\s\S]+?)}/
.exec(stderr2)
.pop()
@ -109,6 +122,10 @@ describe('config telemetry', () => {
expect(event2).toMatch(/"nextConfigOutput": null/)
expect(event2).toMatch(/"trailingSlashEnabled": false/)
expect(event2).toMatch(/"reactStrictMode": false/)
} catch (err) {
require('console').error(stderr2)
throw err
}
})
it('detects output config for session start', async () => {
@ -122,11 +139,16 @@ describe('config telemetry', () => {
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
try {
const event1 = /NEXT_CLI_SESSION_STARTED[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()
expect(event1).toContain('"nextConfigOutput": "export"')
} catch (err) {
require('console').error(stderr)
throw err
}
} finally {
await fs.remove('./next.config.js')
}
@ -143,6 +165,7 @@ describe('config telemetry', () => {
})
await fs.remove(path.join(appDir, '.eslintrc'))
try {
const event1 = /NEXT_LINT_CHECK_COMPLETED[\s\S]+?{([\s\S}]+?)^}/m
.exec(stderr)
.pop()
@ -166,6 +189,10 @@ describe('config telemetry', () => {
featureName: 'build-lint',
invocationCount: 1,
})
} catch (err) {
require('console').error(stderr)
throw err
}
})
it(`emits telemetry for lint during build when '--no-lint' is specified`, async () => {

View file

@ -56,12 +56,18 @@ describe('page features telemetry', () => {
if (app) {
await killApp(app)
}
try {
const event1 = /NEXT_CLI_SESSION_STARTED[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()
expect(event1).toMatch(/"pagesDir": true/)
expect(event1).toMatch(/"turboFlag": true/)
} catch (err) {
require('console').error(stderr)
throw err
}
} finally {
await teardown()
}
@ -191,6 +197,7 @@ describe('page features telemetry', () => {
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
try {
const event1 = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()
@ -209,6 +216,10 @@ describe('page features telemetry', () => {
.pop()
expect(event2).toMatch(/"totalAppPagesCount": 4/)
} catch (err) {
require('console').error(stderr)
throw err
}
} finally {
await teardown()
}
@ -249,10 +260,15 @@ describe('page features telemetry', () => {
path.join(appDir, 'pages', '_app_withreportwebvitals.empty')
)
try {
const event1 = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/
.exec(build.stderr)
.pop()
expect(event1).toMatch(/hasReportWebVitals.*?true/)
} catch (err) {
require('console').error(build.stderr)
throw err
}
})
it('detect without reportWebVitals correctly for `next build`', async () => {
@ -277,9 +293,14 @@ describe('page features telemetry', () => {
path.join(appDir, 'pages', '_app_withoutreportwebvitals.empty')
)
try {
const event1 = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/
.exec(build.stderr)
.pop()
expect(event1).toMatch(/hasReportWebVitals.*?false/)
} catch (err) {
require('console').error(build.stderr)
throw err
}
})
})