fix Next.rs API (#53456)
### What? * fixes problems in Next.rs API introduced by #52846 * adds test infrastructure for experimental turbo testing * adds two test cases to verify the infrastructure * add grouping of output logs in run-tests * simplify template loading ### Why? ### How?
This commit is contained in:
parent
eecd8dc146
commit
61baae126f
27 changed files with 1850 additions and 1745 deletions
27
.github/workflows/build_and_test.yml
vendored
27
.github/workflows/build_and_test.yml
vendored
|
@ -125,6 +125,15 @@ jobs:
|
|||
afterBuild: turbo run rust-check
|
||||
secrets: inherit
|
||||
|
||||
test-experimental-turbopack-dev:
|
||||
name: test experimental turbopack dev
|
||||
needs: ['build-native', 'build-next']
|
||||
uses: ./.github/workflows/build_reusable.yml
|
||||
with:
|
||||
skipForDocsOnly: 'yes'
|
||||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/turbopack-tests-manifest.js" EXPERIMENTAL_TURBOPACK=1 NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_MODE=dev node run-tests.js --test-pattern '^(test\/development)/.*\.test\.(js|jsx|ts|tsx)$' --timings -c ${TEST_CONCURRENCY}
|
||||
secrets: inherit
|
||||
|
||||
test-turbopack-dev:
|
||||
name: test turbopack dev
|
||||
needs: ['build-native', 'build-next']
|
||||
|
@ -134,6 +143,21 @@ jobs:
|
|||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/packages/next-swc/crates/next-dev-tests/tests-manifest.js" TURBOPACK=1 __INTERNAL_CUSTOM_TURBOPACK_BINDINGS="$(pwd)/packages/next-swc/native/next-swc.linux-x64-gnu.node" NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_MODE=dev node run-tests.js --test-pattern '^(test\/development)/.*\.test\.(js|jsx|ts|tsx)$' --timings -c ${TEST_CONCURRENCY}
|
||||
secrets: inherit
|
||||
|
||||
test-experimental-turbopack-integration:
|
||||
name: test experimental turbopack integration
|
||||
needs: ['build-native', 'build-next']
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: [1]
|
||||
|
||||
uses: ./.github/workflows/build_reusable.yml
|
||||
with:
|
||||
nodeVersion: 16
|
||||
skipForDocsOnly: 'yes'
|
||||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/test/turbopack-tests-manifest.js" EXPERIMENTAL_TURBOPACK=1 node run-tests.js --timings -g ${{ matrix.group }}/1 -c ${TEST_CONCURRENCY} --type integration
|
||||
secrets: inherit
|
||||
|
||||
test-turbopack-integration:
|
||||
name: test turbopack integration
|
||||
needs: ['build-native', 'build-next']
|
||||
|
@ -148,7 +172,6 @@ jobs:
|
|||
skipForDocsOnly: 'yes'
|
||||
afterBuild: RUST_BACKTRACE=0 NEXT_EXTERNAL_TESTS_FILTERS="$(pwd)/packages/next-swc/crates/next-dev-tests/tests-manifest.js" TURBOPACK=1 __INTERNAL_CUSTOM_TURBOPACK_BINDINGS="$(pwd)/packages/next-swc/native/next-swc.linux-x64-gnu.node" node run-tests.js --timings -g ${{ matrix.group }}/5 -c ${TEST_CONCURRENCY} --type integration
|
||||
secrets: inherit
|
||||
|
||||
test-next-swc-wasm:
|
||||
name: test next-swc wasm
|
||||
needs: ['build-native', 'build-next']
|
||||
|
@ -244,7 +267,9 @@ jobs:
|
|||
'rust-check',
|
||||
'test-next-swc-wasm',
|
||||
'test-turbopack-dev',
|
||||
'test-experimental-turbopack-dev',
|
||||
'test-turbopack-integration',
|
||||
'test-experimental-turbopack-integration',
|
||||
]
|
||||
|
||||
if: always()
|
||||
|
|
|
@ -451,6 +451,7 @@ impl AppEndpoint {
|
|||
loader_tree,
|
||||
self.app_project.app_dir(),
|
||||
self.pathname.clone(),
|
||||
self.original_name.clone(),
|
||||
self.app_project.project().project_path(),
|
||||
)
|
||||
}
|
||||
|
@ -462,6 +463,7 @@ impl AppEndpoint {
|
|||
self.app_project.edge_rsc_module_context(),
|
||||
Vc::upcast(FileSource::new(path)),
|
||||
self.pathname.clone(),
|
||||
self.original_name.clone(),
|
||||
self.app_project.project().project_path(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -641,7 +641,7 @@ impl PageEndpoint {
|
|||
.project()
|
||||
.node_root()
|
||||
.join("server".to_string()),
|
||||
this.path.root(),
|
||||
this.pages_project.project().project_path(),
|
||||
this.pages_project.ssr_module_context(),
|
||||
this.pages_project.edge_ssr_module_context(),
|
||||
this.pages_project.project().ssr_chunking_context(),
|
||||
|
@ -660,7 +660,7 @@ impl PageEndpoint {
|
|||
.project()
|
||||
.node_root()
|
||||
.join("server-data".to_string()),
|
||||
this.path.root(),
|
||||
this.pages_project.project().project_path(),
|
||||
this.pages_project.ssr_data_module_context(),
|
||||
this.pages_project.edge_ssr_data_module_context(),
|
||||
this.pages_project.project().ssr_data_chunking_context(),
|
||||
|
@ -681,7 +681,7 @@ impl PageEndpoint {
|
|||
.project()
|
||||
.node_root()
|
||||
.join("server".to_string()),
|
||||
this.path.root(),
|
||||
this.pages_project.project().project_path(),
|
||||
this.pages_project.ssr_module_context(),
|
||||
this.pages_project.edge_ssr_module_context(),
|
||||
this.pages_project.project().ssr_chunking_context(),
|
||||
|
|
|
@ -190,7 +190,7 @@ pub async fn get_app_entries(
|
|||
.map(|(pathname, entrypoint)| async move {
|
||||
Ok(match entrypoint {
|
||||
Entrypoint::AppPage {
|
||||
original_name: _,
|
||||
original_name,
|
||||
loader_tree,
|
||||
} => get_app_page_entry(
|
||||
rsc_context,
|
||||
|
@ -199,10 +199,11 @@ pub async fn get_app_entries(
|
|||
*loader_tree,
|
||||
app_dir,
|
||||
pathname.clone(),
|
||||
original_name.clone(),
|
||||
project_root,
|
||||
),
|
||||
Entrypoint::AppRoute {
|
||||
original_name: _,
|
||||
original_name,
|
||||
path,
|
||||
} => get_app_route_entry(
|
||||
rsc_context,
|
||||
|
@ -210,6 +211,7 @@ pub async fn get_app_entries(
|
|||
rsc_context,
|
||||
Vc::upcast(FileSource::new(*path)),
|
||||
pathname.clone(),
|
||||
original_name.clone(),
|
||||
project_root,
|
||||
),
|
||||
})
|
||||
|
|
|
@ -85,6 +85,7 @@ pub async fn get_app_route_favicon_entry(
|
|||
Vc::upcast(source),
|
||||
// TODO(alexkirsz) Get this from the metadata?
|
||||
"/favicon.ico".to_string(),
|
||||
"/favicon.ico".to_string(),
|
||||
project_root,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use turbopack_binding::{
|
|||
turbo::tasks_fs::{rope::RopeBuilder, File, FileSystemPath},
|
||||
turbopack::{
|
||||
core::{
|
||||
asset::AssetContent, context::AssetContext, issue::IssueExt, module::Module,
|
||||
asset::AssetContent, context::AssetContext, issue::IssueExt,
|
||||
reference_type::ReferenceType, virtual_source::VirtualSource,
|
||||
},
|
||||
ecmascript::{chunk::EcmascriptChunkPlaceable, utils::StringifyJs},
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||
next_app::UnsupportedDynamicMetadataIssue,
|
||||
next_server_component::NextServerComponentTransition,
|
||||
parse_segment_config_from_loader_tree,
|
||||
util::{load_next_js, resolve_next_module, NextRuntime},
|
||||
util::{load_next_js_template, virtual_next_js_template_path, NextRuntime},
|
||||
};
|
||||
|
||||
/// Computes the entry for a Next.js app page.
|
||||
|
@ -33,6 +33,7 @@ pub async fn get_app_page_entry(
|
|||
loader_tree: Vc<LoaderTree>,
|
||||
app_dir: Vc<FileSystemPath>,
|
||||
pathname: String,
|
||||
original_name: String,
|
||||
project_root: Vc<FileSystemPath>,
|
||||
) -> Result<Vc<AppEntry>> {
|
||||
let config = parse_segment_config_from_loader_tree(loader_tree, Vc::upcast(nodejs_context));
|
||||
|
@ -77,12 +78,12 @@ pub async fn get_app_page_entry(
|
|||
|
||||
let pages = pages.iter().map(|page| page.to_string()).try_join().await?;
|
||||
|
||||
let original_name = get_original_page_name(&pathname);
|
||||
let original_page_name = get_original_page_name(&original_name);
|
||||
|
||||
let template_file = "/dist/esm/build/webpack/loaders/next-route-loader/templates/app-page.js";
|
||||
let template_file = "build/webpack/loaders/next-route-loader/templates/app-page.js";
|
||||
|
||||
// Load the file from the next.js codebase.
|
||||
let file = load_next_js(project_root, template_file).await?.await?;
|
||||
let file = load_next_js_template(project_root, template_file.to_string()).await?;
|
||||
|
||||
let mut file = file
|
||||
.to_str()?
|
||||
|
@ -96,7 +97,7 @@ pub async fn get_app_page_entry(
|
|||
)
|
||||
.replace(
|
||||
"\"VAR_ORIGINAL_PATHNAME\"",
|
||||
&StringifyJs(&original_name).to_string(),
|
||||
&StringifyJs(&original_page_name).to_string(),
|
||||
)
|
||||
// TODO(alexkirsz) Support custom global error.
|
||||
.replace(
|
||||
|
@ -129,13 +130,7 @@ pub async fn get_app_page_entry(
|
|||
|
||||
let file = File::from(result.build());
|
||||
|
||||
let resolve_result = resolve_next_module(project_root, template_file).await?;
|
||||
|
||||
let Some(template_path) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let template_path = template_path.ident().path();
|
||||
let template_path = virtual_next_js_template_path(project_root, template_file.to_string());
|
||||
|
||||
let source = VirtualSource::new(template_path, AssetContent::file(file.into()));
|
||||
|
||||
|
@ -152,7 +147,7 @@ pub async fn get_app_page_entry(
|
|||
|
||||
Ok(AppEntry {
|
||||
pathname: pathname.to_string(),
|
||||
original_name,
|
||||
original_name: original_page_name,
|
||||
rsc_entry,
|
||||
config,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use turbopack_binding::{
|
|||
core::{
|
||||
asset::AssetContent,
|
||||
context::AssetContext,
|
||||
module::Module,
|
||||
reference_type::{
|
||||
EcmaScriptModulesReferenceSubType, EntryReferenceSubType, ReferenceType,
|
||||
},
|
||||
|
@ -22,7 +21,7 @@ use turbopack_binding::{
|
|||
use crate::{
|
||||
next_app::AppEntry,
|
||||
parse_segment_config_from_source,
|
||||
util::{load_next_js, resolve_next_module, NextRuntime},
|
||||
util::{load_next_js_template, virtual_next_js_template_path, NextRuntime},
|
||||
};
|
||||
|
||||
/// Computes the entry for a Next.js app route.
|
||||
|
@ -32,6 +31,7 @@ pub async fn get_app_route_entry(
|
|||
edge_context: Vc<ModuleAssetContext>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
pathname: String,
|
||||
original_name: String,
|
||||
project_root: Vc<FileSystemPath>,
|
||||
) -> Result<Vc<AppEntry>> {
|
||||
let config = parse_segment_config_from_source(
|
||||
|
@ -49,13 +49,13 @@ pub async fn get_app_route_entry(
|
|||
|
||||
let mut result = RopeBuilder::default();
|
||||
|
||||
let original_name = get_original_route_name(&pathname);
|
||||
let original_page_name = get_original_route_name(&original_name);
|
||||
let path = source.ident().path();
|
||||
|
||||
let template_file = "/dist/esm/build/webpack/loaders/next-route-loader/templates/app-route.js";
|
||||
let template_file = "build/webpack/loaders/next-route-loader/templates/app-route.js";
|
||||
|
||||
// Load the file from the next.js codebase.
|
||||
let file = load_next_js(project_root, template_file).await?.await?;
|
||||
let file = load_next_js_template(project_root, template_file.to_string()).await?;
|
||||
|
||||
let mut file = file
|
||||
.to_str()?
|
||||
|
@ -78,7 +78,7 @@ pub async fn get_app_route_entry(
|
|||
)
|
||||
.replace(
|
||||
"\"VAR_ORIGINAL_PATHNAME\"",
|
||||
&StringifyJs(&original_name).to_string(),
|
||||
&StringifyJs(&original_page_name).to_string(),
|
||||
)
|
||||
.replace(
|
||||
"\"VAR_RESOLVED_PAGE_PATH\"",
|
||||
|
@ -98,13 +98,7 @@ pub async fn get_app_route_entry(
|
|||
|
||||
let file = File::from(result.build());
|
||||
|
||||
let resolve_result = resolve_next_module(project_root, template_file).await?;
|
||||
|
||||
let Some(template_path) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let template_path = template_path.ident().path();
|
||||
let template_path = virtual_next_js_template_path(project_root, template_file.to_string());
|
||||
|
||||
let virtual_source = VirtualSource::new(template_path, AssetContent::file(file.into()));
|
||||
|
||||
|
@ -132,7 +126,7 @@ pub async fn get_app_route_entry(
|
|||
|
||||
Ok(AppEntry {
|
||||
pathname: pathname.to_string(),
|
||||
original_name,
|
||||
original_name: original_page_name,
|
||||
rsc_entry,
|
||||
config,
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
|||
issue::NextFontIssue,
|
||||
util::{get_scoped_font_family, FontFamilyType},
|
||||
},
|
||||
util::load_next_json,
|
||||
util::load_next_js_templateon,
|
||||
};
|
||||
|
||||
/// An entry in the Google fonts metrics map
|
||||
|
@ -54,8 +54,11 @@ pub(super) async fn get_font_fallback(
|
|||
Ok(match &options.fallback {
|
||||
Some(fallback) => FontFallback::Manual(Vc::cell(fallback.clone())).cell(),
|
||||
None => {
|
||||
let metrics_json =
|
||||
load_next_json(context, "/dist/server/capsize-font-metrics.json").await?;
|
||||
let metrics_json = load_next_js_templateon(
|
||||
context,
|
||||
"dist/server/capsize-font-metrics.json".to_string(),
|
||||
)
|
||||
.await?;
|
||||
let fallback = lookup_fallback(
|
||||
&options.font_family,
|
||||
metrics_json,
|
||||
|
|
|
@ -48,7 +48,7 @@ use super::{
|
|||
get_request_hash, get_request_id, get_scoped_font_family, FontCssProperties, FontFamilyType,
|
||||
},
|
||||
};
|
||||
use crate::{embed_js::next_js_file_path, util::load_next_json};
|
||||
use crate::{embed_js::next_js_file_path, util::load_next_js_templateon};
|
||||
|
||||
pub mod font_fallback;
|
||||
pub mod options;
|
||||
|
@ -266,9 +266,9 @@ impl ImportMappingReplacement for NextFontGoogleCssModuleReplacer {
|
|||
|
||||
#[turbo_tasks::function]
|
||||
async fn load_font_data(project_root: Vc<FileSystemPath>) -> Result<Vc<FontData>> {
|
||||
let data: FontData = load_next_json(
|
||||
let data: FontData = load_next_js_templateon(
|
||||
project_root,
|
||||
"/dist/compiled/@next/font/dist/google/font-data.json",
|
||||
"dist/compiled/@next/font/dist/google/font-data.json".to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -229,6 +229,16 @@ pub async fn get_next_server_import_map(
|
|||
ServerContextType::AppSSR { .. }
|
||||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. } => {
|
||||
match mode {
|
||||
NextMode::Development | NextMode::Build => {
|
||||
import_map.insert_wildcard_alias("next/dist/server/", external);
|
||||
import_map.insert_wildcard_alias("next/dist/shared/", external);
|
||||
}
|
||||
NextMode::DevServer => {
|
||||
// The sandbox can't be bundled and needs to be external
|
||||
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
|
||||
}
|
||||
}
|
||||
import_map.insert_exact_alias(
|
||||
"next/head",
|
||||
request_to_import_mapping(project_path, "next/dist/client/components/noop-head"),
|
||||
|
@ -237,9 +247,6 @@ pub async fn get_next_server_import_map(
|
|||
"next/dynamic",
|
||||
request_to_import_mapping(project_path, "next/dist/shared/lib/app-dynamic"),
|
||||
);
|
||||
|
||||
// The sandbox can't be bundled and needs to be external
|
||||
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
|
||||
}
|
||||
ServerContextType::Middleware => {}
|
||||
}
|
||||
|
@ -620,17 +627,19 @@ async fn package_lookup_resolve_options(
|
|||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub async fn get_next_package(project_path: Vc<FileSystemPath>) -> Result<Vc<FileSystemPath>> {
|
||||
pub async fn get_next_package(context_directory: Vc<FileSystemPath>) -> Result<Vc<FileSystemPath>> {
|
||||
let result = resolve(
|
||||
project_path,
|
||||
context_directory,
|
||||
Request::parse(Value::new(Pattern::Constant(
|
||||
"next/package.json".to_string(),
|
||||
))),
|
||||
package_lookup_resolve_options(project_path),
|
||||
package_lookup_resolve_options(context_directory),
|
||||
);
|
||||
let assets = result.primary_sources().await?;
|
||||
let asset = *assets.first().context("Next.js package not found")?;
|
||||
Ok(asset.ident().path().parent())
|
||||
let source = result
|
||||
.first_source()
|
||||
.await?
|
||||
.context("Next.js package not found")?;
|
||||
Ok(source.ident().path().parent())
|
||||
}
|
||||
|
||||
pub async fn insert_alias_option<const N: usize>(
|
||||
|
|
|
@ -13,7 +13,6 @@ use turbopack_binding::{
|
|||
core::{
|
||||
asset::AssetContent,
|
||||
context::AssetContext,
|
||||
module::Module,
|
||||
reference_type::{EntryReferenceSubType, ReferenceType},
|
||||
source::Source,
|
||||
virtual_source::VirtualSource,
|
||||
|
@ -22,7 +21,7 @@ use turbopack_binding::{
|
|||
},
|
||||
};
|
||||
|
||||
use crate::util::{load_next_js, resolve_next_module};
|
||||
use crate::util::{load_next_js_template, virtual_next_js_template_path};
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub async fn create_page_ssr_entry_module(
|
||||
|
@ -43,17 +42,17 @@ pub async fn create_page_ssr_entry_module(
|
|||
let template_file = match reference_type {
|
||||
ReferenceType::Entry(EntryReferenceSubType::Page) => {
|
||||
// Load the Page entry file.
|
||||
"/dist/esm/build/webpack/loaders/next-route-loader/templates/pages.js"
|
||||
"build/webpack/loaders/next-route-loader/templates/pages.js"
|
||||
}
|
||||
ReferenceType::Entry(EntryReferenceSubType::PagesApi) => {
|
||||
// Load the Pages API entry file.
|
||||
"/dist/esm/build/webpack/loaders/next-route-loader/templates/pages-api.js"
|
||||
"build/webpack/loaders/next-route-loader/templates/pages-api.js"
|
||||
}
|
||||
_ => bail!("Invalid path type"),
|
||||
};
|
||||
|
||||
// Load the file from the next.js codebase.
|
||||
let file = load_next_js(project_root, template_file).await?.await?;
|
||||
let file = load_next_js_template(project_root, template_file.to_string()).await?;
|
||||
|
||||
let mut file = file
|
||||
.to_str()?
|
||||
|
@ -103,13 +102,7 @@ pub async fn create_page_ssr_entry_module(
|
|||
|
||||
let file = File::from(result.build());
|
||||
|
||||
let resolve_result = resolve_next_module(project_root, template_file).await?;
|
||||
|
||||
let Some(template_path) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let template_path = template_path.ident().path();
|
||||
let template_path = virtual_next_js_template_path(project_root, template_file.to_string());
|
||||
|
||||
let source = VirtualSource::new(template_path, AssetContent::file(file.into()));
|
||||
|
||||
|
|
|
@ -2,22 +2,16 @@ use anyhow::{bail, Result};
|
|||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use swc_core::ecma::ast::Program;
|
||||
use turbo_tasks::{trace::TraceRawVcs, TaskInput, Value, ValueDefault, ValueToString, Vc};
|
||||
use turbo_tasks::{trace::TraceRawVcs, TaskInput, ValueDefault, ValueToString, Vc};
|
||||
use turbo_tasks_fs::rope::Rope;
|
||||
use turbopack_binding::{
|
||||
turbo::tasks_fs::{json::parse_json_rope_with_source_context, FileContent, FileSystemPath},
|
||||
turbopack::{
|
||||
core::{
|
||||
asset::Asset,
|
||||
environment::{ServerAddr, ServerInfo},
|
||||
ident::AssetIdent,
|
||||
issue::{Issue, IssueExt, IssueSeverity, OptionIssueSource},
|
||||
issue::{Issue, IssueExt, IssueSeverity},
|
||||
module::Module,
|
||||
reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
|
||||
resolve::{
|
||||
self, handle_resolve_error, node::node_cjs_resolve_options, parse::Request,
|
||||
ModuleResolveResult,
|
||||
},
|
||||
},
|
||||
ecmascript::{
|
||||
analyzer::{JsValue, ObjectPart},
|
||||
|
@ -28,7 +22,10 @@ use turbopack_binding::{
|
|||
},
|
||||
};
|
||||
|
||||
use crate::next_config::{NextConfig, OutputType};
|
||||
use crate::{
|
||||
next_config::{NextConfig, OutputType},
|
||||
next_import_map::get_next_package,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TaskInput)]
|
||||
pub enum PathType {
|
||||
|
@ -333,14 +330,16 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
|
|||
config
|
||||
}
|
||||
|
||||
pub async fn load_next_js(context: Vc<FileSystemPath>, path: &str) -> Result<Vc<Rope>> {
|
||||
let resolve_result = resolve_next_module(context, path).await?;
|
||||
#[turbo_tasks::function]
|
||||
pub async fn load_next_js_template(
|
||||
project_path: Vc<FileSystemPath>,
|
||||
path: String,
|
||||
) -> Result<Vc<Rope>> {
|
||||
let file_path = get_next_package(project_path)
|
||||
.join("dist/esm".to_string())
|
||||
.join(path);
|
||||
|
||||
let Some(js_asset) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let content = &*js_asset.content().file_content().await?;
|
||||
let content = &*file_path.read().await?;
|
||||
|
||||
let FileContent::Content(file) = content else {
|
||||
bail!("Expected file content for file");
|
||||
|
@ -349,44 +348,23 @@ pub async fn load_next_js(context: Vc<FileSystemPath>, path: &str) -> Result<Vc<
|
|||
Ok(file.content().to_owned().cell())
|
||||
}
|
||||
|
||||
pub async fn resolve_next_module(
|
||||
context: Vc<FileSystemPath>,
|
||||
path: &str,
|
||||
) -> Result<Vc<ModuleResolveResult>> {
|
||||
let request = Request::module(
|
||||
"next".to_owned(),
|
||||
Value::new(path.to_string().into()),
|
||||
Vc::cell(None),
|
||||
);
|
||||
let resolve_options = node_cjs_resolve_options(context.root());
|
||||
|
||||
let resolve_result = handle_resolve_error(
|
||||
resolve::resolve(context, request, resolve_options).as_raw_module_result(),
|
||||
Value::new(ReferenceType::EcmaScriptModules(
|
||||
EcmaScriptModulesReferenceSubType::Undefined,
|
||||
)),
|
||||
context,
|
||||
request,
|
||||
resolve_options,
|
||||
OptionIssueSource::none(),
|
||||
IssueSeverity::Error.cell(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(resolve_result)
|
||||
#[turbo_tasks::function]
|
||||
pub fn virtual_next_js_template_path(
|
||||
project_path: Vc<FileSystemPath>,
|
||||
path: String,
|
||||
) -> Vc<FileSystemPath> {
|
||||
get_next_package(project_path)
|
||||
.join("dist/esm".to_string())
|
||||
.join(path)
|
||||
}
|
||||
|
||||
pub async fn load_next_json<T: DeserializeOwned>(
|
||||
context: Vc<FileSystemPath>,
|
||||
path: &str,
|
||||
pub async fn load_next_js_templateon<T: DeserializeOwned>(
|
||||
project_path: Vc<FileSystemPath>,
|
||||
path: String,
|
||||
) -> Result<T> {
|
||||
let resolve_result = resolve_next_module(context, path).await?;
|
||||
let file_path = get_next_package(project_path).join(path);
|
||||
|
||||
let Some(metrics_asset) = *resolve_result.first_module().await? else {
|
||||
bail!("Expected to find module");
|
||||
};
|
||||
|
||||
let content = &*metrics_asset.content().file_content().await?;
|
||||
let content = &*file_path.read().await?;
|
||||
|
||||
let FileContent::Content(file) = content else {
|
||||
bail!("Expected file content for metrics data");
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import type { LoaderTree } from '../../../../../server/lib/app-dir-module'
|
||||
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/app-page/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
import { AppPageRouteModule } from '../../../../../server/future/route-modules/app-page/module'
|
||||
|
||||
const AppPageRouteModule =
|
||||
module.AppPageRouteModule as unknown as typeof import('../../../../../server/future/route-modules/app-page/module').AppPageRouteModule
|
||||
|
||||
// These are injected by the loader afterwards.
|
||||
declare const tree: LoaderTree
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import '../../../../../server/node-polyfill-headers'
|
||||
|
||||
import {
|
||||
AppRouteRouteModule,
|
||||
type AppRouteRouteModuleOptions,
|
||||
} from '../../../../../server/future/route-modules/app-route/module'
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/app-route/module'
|
||||
import type { AppRouteRouteModuleOptions } from '../../../../../server/future/route-modules/app-route/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
|
||||
// @ts-expect-error - replaced by webpack/turbopack loader
|
||||
import * as userland from 'VAR_USERLAND'
|
||||
|
||||
const AppRouteRouteModule =
|
||||
module.AppRouteRouteModule as unknown as typeof import('../../../../../server/future/route-modules/app-route/module').AppRouteRouteModule
|
||||
|
||||
// These are injected by the loader afterwards. This is injected as a variable
|
||||
// instead of a replacement because this could also be `undefined` instead of
|
||||
// an empty string.
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { PagesAPIRouteModule } from '../../../../../server/future/route-modules/pages-api/module'
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/pages-api/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
import { hoist } from '../helpers'
|
||||
|
||||
const PagesAPIRouteModule =
|
||||
module.PagesAPIRouteModule as unknown as typeof import('../../../../../server/future/route-modules/pages-api/module').PagesAPIRouteModule
|
||||
|
||||
// Import the userland code.
|
||||
// @ts-expect-error - replaced by webpack/turbopack loader
|
||||
import * as userland from 'VAR_USERLAND'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { PagesRouteModule } from '../../../../../server/future/route-modules/pages/module'
|
||||
// @ts-ignore this need to be imported from next/dist to be external
|
||||
import * as module from 'next/dist/server/future/route-modules/pages/module'
|
||||
import { RouteKind } from '../../../../../server/future/route-kind'
|
||||
import { hoist } from '../helpers'
|
||||
|
||||
|
@ -12,6 +13,9 @@ import App from 'VAR_MODULE_APP'
|
|||
// @ts-expect-error - replaced by webpack/turbopack loader
|
||||
import * as userland from 'VAR_USERLAND'
|
||||
|
||||
const PagesRouteModule =
|
||||
module.PagesRouteModule as unknown as typeof import('../../../../../server/future/route-modules/pages/module').PagesRouteModule
|
||||
|
||||
// Re-export the component (should be the default export).
|
||||
export default hoist(userland, 'default')
|
||||
|
||||
|
|
35
run-tests.js
35
run-tests.js
|
@ -12,6 +12,9 @@ const { createNextInstall } = require('./test/lib/create-next-install')
|
|||
const glob = promisify(_glob)
|
||||
const exec = promisify(execOrig)
|
||||
|
||||
const GROUP = process.env.CI ? '##[group]' : ''
|
||||
const ENDGROUP = process.env.CI ? '##[endgroup]' : ''
|
||||
|
||||
// Try to read an external array-based json to filter tests to be allowed / or disallowed.
|
||||
// If process.argv contains a test to be executed, this'll append it to the list.
|
||||
const externalTestsFilterLists = process.env.NEXT_EXTERNAL_TESTS_FILTERS
|
||||
|
@ -272,7 +275,9 @@ async function main() {
|
|||
return cleanUpAndExit(1)
|
||||
}
|
||||
|
||||
console.log('Running tests:', '\n', ...testNames.map((name) => `${name}\n`))
|
||||
console.log(`${GROUP}Running tests:
|
||||
${testNames.join('\n')}
|
||||
${ENDGROUP}`)
|
||||
|
||||
const hasIsolatedTests = testNames.some((test) => {
|
||||
return configuredTestTypes.some(
|
||||
|
@ -288,7 +293,7 @@ async function main() {
|
|||
// for isolated next tests: e2e, dev, prod we create
|
||||
// a starter Next.js install to re-use to speed up tests
|
||||
// to avoid having to run yarn each time
|
||||
console.log('Creating Next.js install for isolated tests')
|
||||
console.log(`${GROUP}Creating Next.js install for isolated tests`)
|
||||
const reactVersion = process.env.NEXT_TEST_REACT_VERSION || 'latest'
|
||||
const { installDir, pkgPaths, tmpRepoDir } = await createNextInstall({
|
||||
parentSpan: mockTrace(),
|
||||
|
@ -307,9 +312,11 @@ async function main() {
|
|||
process.env.NEXT_TEST_PKG_PATHS = JSON.stringify(serializedPkgPaths)
|
||||
process.env.NEXT_TEST_TEMP_REPO = tmpRepoDir
|
||||
process.env.NEXT_TEST_STARTER = installDir
|
||||
console.log(`${ENDGROUP}`)
|
||||
}
|
||||
|
||||
const sema = new Sema(concurrency, { capacity: testNames.length })
|
||||
const outputSema = new Sema(1, { capacity: testNames.length })
|
||||
const children = new Set()
|
||||
const jestPath = path.join(
|
||||
__dirname,
|
||||
|
@ -374,7 +381,7 @@ async function main() {
|
|||
if (hideOutput) {
|
||||
outputChunks.push({ type, chunk })
|
||||
} else {
|
||||
process.stderr.write(chunk)
|
||||
process.stdout.write(chunk)
|
||||
}
|
||||
}
|
||||
child.stdout.on('data', handleOutput('stdout'))
|
||||
|
@ -386,20 +393,22 @@ async function main() {
|
|||
children.delete(child)
|
||||
if (code !== 0 || signal !== null) {
|
||||
if (hideOutput) {
|
||||
await outputSema.acquire()
|
||||
process.stdout.write(`${GROUP}${test} output\n`)
|
||||
// limit out to last 64kb so that we don't
|
||||
// run out of log room in CI
|
||||
outputChunks.forEach(({ type, chunk }) => {
|
||||
if (type === 'stdout') {
|
||||
process.stdout.write(chunk)
|
||||
} else {
|
||||
process.stderr.write(chunk)
|
||||
}
|
||||
})
|
||||
for (const { chunk } of outputChunks) {
|
||||
process.stdout.write(chunk)
|
||||
}
|
||||
process.stdout.write(`end of ${test} output\n${ENDGROUP}\n`)
|
||||
outputSema.release()
|
||||
}
|
||||
const err = new Error(
|
||||
code ? `failed with code: ${code}` : `failed with signal: ${signal}`
|
||||
)
|
||||
err.output = outputChunks.map((chunk) => chunk.toString()).join('')
|
||||
err.output = outputChunks
|
||||
.map(({ chunk }) => chunk.toString())
|
||||
.join('')
|
||||
|
||||
return reject(err)
|
||||
}
|
||||
|
@ -498,11 +507,15 @@ async function main() {
|
|||
if ((!passed || shouldContinueTestsOnError) && isTestJob) {
|
||||
try {
|
||||
const testsOutput = await fs.readFile(`${test}${RESULTS_EXT}`, 'utf8')
|
||||
await outputSema.acquire()
|
||||
if (GROUP) console.log(`${GROUP}Result as JSON for tooling`)
|
||||
console.log(
|
||||
`--test output start--`,
|
||||
testsOutput,
|
||||
`--test output end--`
|
||||
)
|
||||
if (ENDGROUP) console.log(ENDGROUP)
|
||||
outputSema.release()
|
||||
} catch (err) {
|
||||
console.log(`Failed to load test output`, err)
|
||||
}
|
||||
|
|
|
@ -5,62 +5,64 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
|
||||
// TODO-APP: Investigate snapshot mismatch
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
skipStart: true,
|
||||
})
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox app %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
// Module trace is only available with webpack 5
|
||||
test('Node.js builtins', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'node_modules/my-package/index.js',
|
||||
outdent`
|
||||
// Module trace is only available with webpack 5
|
||||
test('Node.js builtins', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'node_modules/my-package/index.js',
|
||||
outdent`
|
||||
const dns = require('dns')
|
||||
module.exports = dns
|
||||
`,
|
||||
],
|
||||
[
|
||||
'node_modules/my-package/package.json',
|
||||
outdent`
|
||||
],
|
||||
[
|
||||
'node_modules/my-package/package.json',
|
||||
outdent`
|
||||
{
|
||||
"name": "my-package",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
`,
|
||||
],
|
||||
])
|
||||
)
|
||||
],
|
||||
])
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import pkg from 'my-package'
|
||||
|
||||
export default function Hello() {
|
||||
return (pkg ? <h1>Package loaded</h1> : <h1>Package did not load</h1>)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('Module not found', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('Module not found', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import Comp from 'b'
|
||||
export default function Oops() {
|
||||
return (
|
||||
|
@ -70,22 +72,22 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('Module not found empty import trace', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('Module not found empty import trace', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'app/page.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'app/page.js',
|
||||
outdent`
|
||||
'use client'
|
||||
import Comp from 'b'
|
||||
export default function Oops() {
|
||||
|
@ -96,51 +98,52 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('Module not found missing global CSS', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'app/page.js',
|
||||
outdent`
|
||||
test('Module not found missing global CSS', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'app/page.js',
|
||||
outdent`
|
||||
'use client'
|
||||
import './non-existent.css'
|
||||
export default function Page(props) {
|
||||
return <p>index page</p>
|
||||
}
|
||||
`,
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'app/page.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'app/page.js',
|
||||
outdent`
|
||||
'use client'
|
||||
export default function Page(props) {
|
||||
return <p>index page</p>
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.documentElement.innerHTML)
|
||||
).toContain('index page')
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.documentElement.innerHTML)
|
||||
).toContain('index page')
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,22 +5,24 @@ import { check, describeVariants as describe } from 'next-test-utils'
|
|||
import path from 'path'
|
||||
import { outdent } from 'outdent'
|
||||
|
||||
describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
skipStart: true,
|
||||
})
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'Error recovery app %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
dependencies: {
|
||||
react: 'latest',
|
||||
'react-dom': 'latest',
|
||||
},
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
test('can recover from a syntax error without losing state', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('can recover from a syntax error without losing state', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -34,23 +36,23 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
|
||||
await session.patch('index.js', `export default () => <div/`)
|
||||
await session.patch('index.js', `export default () => <div/`)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default () => <div/'
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default () => <div/'
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -64,58 +66,58 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await check(
|
||||
() => session.evaluate(() => document.querySelector('p').textContent),
|
||||
/Count: 1/
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test.each([['client'], ['server']])(
|
||||
'%s component can recover from syntax error',
|
||||
async (type: string) => {
|
||||
const { session, browser, cleanup } = await sandbox(
|
||||
next,
|
||||
undefined,
|
||||
'/' + type
|
||||
)
|
||||
|
||||
// Add syntax error
|
||||
await session.patch(
|
||||
`app/${type}/page.js`,
|
||||
outdent`
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await check(
|
||||
() => session.evaluate(() => document.querySelector('p').textContent),
|
||||
/Count: 1/
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test.each([['client'], ['server']])(
|
||||
'%s component can recover from syntax error',
|
||||
async (type: string) => {
|
||||
const { session, browser, cleanup } = await sandbox(
|
||||
next,
|
||||
undefined,
|
||||
'/' + type
|
||||
)
|
||||
|
||||
// Add syntax error
|
||||
await session.patch(
|
||||
`app/${type}/page.js`,
|
||||
outdent`
|
||||
export default function Page() {
|
||||
return <p>Hello world</p>
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
// Fix syntax error
|
||||
await session.patch(
|
||||
`app/${type}/page.js`,
|
||||
outdent`
|
||||
// Fix syntax error
|
||||
await session.patch(
|
||||
`app/${type}/page.js`,
|
||||
outdent`
|
||||
export default function Page() {
|
||||
return <p>Hello world 2</p>
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await check(() => browser.elementByCss('p').text(), 'Hello world 2')
|
||||
await cleanup()
|
||||
}
|
||||
)
|
||||
await check(() => browser.elementByCss('p').text(), 'Hello world 2')
|
||||
await cleanup()
|
||||
}
|
||||
)
|
||||
|
||||
test('can recover from a event handler error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('can recover from a event handler error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -132,18 +134,18 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('0')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('0')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
|
||||
await session.waitForAndOpenRuntimeError()
|
||||
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
|
||||
await session.waitForAndOpenRuntimeError()
|
||||
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
|
||||
"index.js (7:10) @ eval
|
||||
|
||||
5 | const increment = useCallback(() => {
|
||||
|
@ -155,9 +157,9 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
10 | <main>"
|
||||
`)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -171,46 +173,46 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasErrorToast()).toBe(false)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 1')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 2')
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasErrorToast()).toBe(false)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test.each([['client'], ['server']])(
|
||||
'%s component can recover from a component error',
|
||||
async (type: string) => {
|
||||
const { session, cleanup, browser } = await sandbox(
|
||||
next,
|
||||
undefined,
|
||||
'/' + type
|
||||
)
|
||||
|
||||
await session.write(
|
||||
'child.js',
|
||||
outdent`
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasErrorToast()).toBe(false)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 1')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 2')
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasErrorToast()).toBe(false)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test.each([['client'], ['server']])(
|
||||
'%s component can recover from a component error',
|
||||
async (type: string) => {
|
||||
const { session, cleanup, browser } = await sandbox(
|
||||
next,
|
||||
undefined,
|
||||
'/' + type
|
||||
)
|
||||
|
||||
await session.write(
|
||||
'child.js',
|
||||
outdent`
|
||||
export default function Child() {
|
||||
return <p>Hello</p>;
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import Child from './child'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -221,121 +223,121 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await browser.elementByCss('p').text()).toBe('Hello')
|
||||
expect(await browser.elementByCss('p').text()).toBe('Hello')
|
||||
|
||||
await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
// hello
|
||||
export default function Child() {
|
||||
throw new Error('oops')
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default function Child()'
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default function Child()'
|
||||
)
|
||||
|
||||
// TODO-APP: re-enable when error recovery doesn't reload the page.
|
||||
/* const didNotReload = */ await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
// TODO-APP: re-enable when error recovery doesn't reload the page.
|
||||
/* const didNotReload = */ await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
export default function Child() {
|
||||
return <p>Hello</p>;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// TODO-APP: re-enable when error recovery doesn't reload the page.
|
||||
// expect(didNotReload).toBe(true)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Hello')
|
||||
|
||||
await cleanup()
|
||||
}
|
||||
)
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098
|
||||
test('syntax > runtime error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// Start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
export default function FunctionNamed() {
|
||||
return <div />
|
||||
}
|
||||
`
|
||||
)
|
||||
// TODO: this acts weird without above step
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
i++
|
||||
throw Error('no ' + i)
|
||||
}, 1000)
|
||||
export default function FunctionNamed() {
|
||||
return <div />
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// TODO-APP: re-enable when error recovery doesn't reload the page.
|
||||
// expect(didNotReload).toBe(true)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Hello')
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
await session.waitForAndOpenRuntimeError()
|
||||
expect(await session.getRedboxSource()).not.toInclude(
|
||||
"Expected '}', got '<eof>'"
|
||||
)
|
||||
|
||||
// Make a syntax error.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
i++
|
||||
throw Error('no ' + i)
|
||||
}, 1000)
|
||||
export default function FunctionNamed() {
|
||||
`
|
||||
)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"Expected '}', got '<eof>'"
|
||||
)
|
||||
|
||||
// Test that runtime error does not take over:
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"Expected '}', got '<eof>'"
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098
|
||||
test('syntax > runtime error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016
|
||||
test('stuck error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// Start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
export default function FunctionNamed() {
|
||||
return <div />
|
||||
}
|
||||
`
|
||||
)
|
||||
// TODO: this acts weird without above step
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
i++
|
||||
throw Error('no ' + i)
|
||||
}, 1000)
|
||||
export default function FunctionNamed() {
|
||||
return <div />
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
await session.waitForAndOpenRuntimeError()
|
||||
expect(await session.getRedboxSource()).not.toInclude(
|
||||
"Expected '}', got '<eof>'"
|
||||
)
|
||||
|
||||
// Make a syntax error.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
i++
|
||||
throw Error('no ' + i)
|
||||
}, 1000)
|
||||
export default function FunctionNamed() {
|
||||
`
|
||||
)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"Expected '}', got '<eof>'"
|
||||
)
|
||||
|
||||
// Test that runtime error does not take over:
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"Expected '}', got '<eof>'"
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016
|
||||
test('stuck error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// We start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// We start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
function FunctionDefault() {
|
||||
|
@ -344,23 +346,23 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
|
||||
export default FunctionDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// We add a new file. Let's call it Foo.js.
|
||||
await session.write(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
// We add a new file. Let's call it Foo.js.
|
||||
await session.write(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
// intentionally skips export
|
||||
export default function Foo() {
|
||||
return React.createElement('h1', null, 'Foo');
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// We edit our first file to use it.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// We edit our first file to use it.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
import Foo from './Foo';
|
||||
function FunctionDefault() {
|
||||
|
@ -368,39 +370,39 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
}
|
||||
export default FunctionDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// We get an error because Foo didn't import React. Fair.
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"return React.createElement('h1', null, 'Foo');"
|
||||
)
|
||||
// We get an error because Foo didn't import React. Fair.
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"return React.createElement('h1', null, 'Foo');"
|
||||
)
|
||||
|
||||
// Let's add that to Foo.
|
||||
await session.patch(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
// Let's add that to Foo.
|
||||
await session.patch(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
export default function Foo() {
|
||||
return React.createElement('h1', null, 'Foo');
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// Expected: this fixes the problem
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
// Expected: this fixes the problem
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262
|
||||
test('render error not shown right after syntax error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262
|
||||
test('render error not shown right after syntax error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// Starting here:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Starting here:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
class ClassDefault extends React.Component {
|
||||
render() {
|
||||
|
@ -410,16 +412,16 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h1').textContent)
|
||||
).toBe('Default Export')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h1').textContent)
|
||||
).toBe('Default Export')
|
||||
|
||||
// Break it with a syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Break it with a syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
class ClassDefault extends React.Component {
|
||||
|
@ -430,13 +432,13 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
// Now change the code to introduce a runtime error without fixing the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Now change the code to introduce a runtime error without fixing the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
class ClassDefault extends React.Component {
|
||||
|
@ -448,13 +450,13 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
// Now fix the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Now fix the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
class ClassDefault extends React.Component {
|
||||
|
@ -466,30 +468,31 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
await check(async () => {
|
||||
const source = await session.getRedboxSource()
|
||||
return source?.includes('render() {') ? 'success' : source
|
||||
}, 'success')
|
||||
await check(async () => {
|
||||
const source = await session.getRedboxSource()
|
||||
return source?.includes('render() {') ? 'success' : source
|
||||
}, 'success')
|
||||
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"throw new Error('nooo');"
|
||||
)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"throw new Error('nooo');"
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('displays build error on initial page load', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([['app/page.js', '{{{']])
|
||||
)
|
||||
test('displays build error on initial page load', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([['app/page.js', '{{{']])
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
await check(() => session.getRedboxSource(true), /Failed to compile/)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
await check(() => session.getRedboxSource(true), /Failed to compile/)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -4,48 +4,50 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
import path from 'path'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
test('empty _app shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([['pages/_app.js', ``]])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: The default export is not a React Component in page: \\"/_app\\""`
|
||||
)
|
||||
test('empty _app shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([['pages/_app.js', ``]])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: The default export is not a React Component in page: \\"/_app\\""`
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
export default MyApp
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('empty _document shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([['pages/_document.js', ``]])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: The default export is not a React Component in page: \\"/_document\\""`
|
||||
)
|
||||
test('empty _document shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([['pages/_document.js', ``]])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: The default export is not a React Component in page: \\"/_document\\""`
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'pages/_document.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'pages/_document.js',
|
||||
outdent`
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
class MyDocument extends Document {
|
||||
|
@ -69,31 +71,31 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default MyDocument
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('_app syntax error shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
test('_app syntax error shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <<Component {...pageProps} />;
|
||||
}
|
||||
export default MyApp
|
||||
`,
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
"./pages/_app.js
|
||||
Error:
|
||||
x Expression expected
|
||||
|
@ -117,28 +119,28 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
Caused by:
|
||||
Syntax Error"
|
||||
`)
|
||||
)
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
export default MyApp
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('_document syntax error shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'pages/_document.js',
|
||||
outdent`
|
||||
test('_document syntax error shows logbox', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'pages/_document.js',
|
||||
outdent`
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
class MyDocument extends Document {{
|
||||
|
@ -162,14 +164,14 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default MyDocument
|
||||
`,
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
"./pages/_document.js
|
||||
Error:
|
||||
x Unexpected token \`{\`. Expected identifier, string literal, numeric literal or [ for the computed key
|
||||
|
@ -186,11 +188,11 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
Caused by:
|
||||
Syntax Error"
|
||||
`)
|
||||
)
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'pages/_document.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'pages/_document.js',
|
||||
outdent`
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
class MyDocument extends Document {
|
||||
|
@ -214,8 +216,9 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default MyDocument
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await cleanup()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -4,58 +4,60 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
import path from 'path'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
// Module trace is only available with webpack 5
|
||||
test('Node.js builtins', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'node_modules/my-package/index.js',
|
||||
outdent`
|
||||
// Module trace is only available with webpack 5
|
||||
test('Node.js builtins', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'node_modules/my-package/index.js',
|
||||
outdent`
|
||||
const dns = require('dns')
|
||||
module.exports = dns
|
||||
`,
|
||||
],
|
||||
[
|
||||
'node_modules/my-package/package.json',
|
||||
outdent`
|
||||
],
|
||||
[
|
||||
'node_modules/my-package/package.json',
|
||||
outdent`
|
||||
{
|
||||
"name": "my-package",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
`,
|
||||
],
|
||||
])
|
||||
)
|
||||
],
|
||||
])
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import pkg from 'my-package'
|
||||
|
||||
export default function Hello() {
|
||||
return (pkg ? <h1>Package loaded</h1> : <h1>Package did not load</h1>)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('Module not found', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('Module not found', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import Comp from 'b'
|
||||
|
||||
export default function Oops() {
|
||||
|
@ -66,22 +68,22 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('Module not found (empty import trace)', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('Module not found (empty import trace)', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'pages/index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'pages/index.js',
|
||||
outdent`
|
||||
import Comp from 'b'
|
||||
|
||||
export default function Oops() {
|
||||
|
@ -92,58 +94,59 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('Module not found (missing global CSS)', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
test('Module not found (missing global CSS)', async () => {
|
||||
const { session, cleanup } = await sandbox(
|
||||
next,
|
||||
new Map([
|
||||
[
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
import './non-existent.css'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
`,
|
||||
],
|
||||
[
|
||||
'pages/index.js',
|
||||
outdent`
|
||||
],
|
||||
[
|
||||
'pages/index.js',
|
||||
outdent`
|
||||
export default function Page(props) {
|
||||
return <p>index page</p>
|
||||
}
|
||||
`,
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
],
|
||||
])
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'pages/_app.js',
|
||||
outdent`
|
||||
export default function App({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.documentElement.innerHTML)
|
||||
).toContain('index page')
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.documentElement.innerHTML)
|
||||
).toContain('index page')
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,18 +5,20 @@ import { describeVariants as describe } from 'next-test-utils'
|
|||
import path from 'path'
|
||||
import { outdent } from 'outdent'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
test('should strip whitespace correctly with newline', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('should strip whitespace correctly with newline', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
|
@ -32,24 +34,24 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
await session.evaluate(() => document.querySelector('a').click())
|
||||
)
|
||||
await session.evaluate(() => document.querySelector('a').click())
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807
|
||||
test('module init error not shown', async () => {
|
||||
// Start here:
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137807
|
||||
test('module init error not shown', async () => {
|
||||
// Start here:
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// We start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// We start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
class ClassDefault extends React.Component {
|
||||
render() {
|
||||
|
@ -58,16 +60,16 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
}
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h1').textContent)
|
||||
).toBe('Default Export')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h1').textContent)
|
||||
).toBe('Default Export')
|
||||
|
||||
// Add a throw in module init phase:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Add a throw in module init phase:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// top offset for snapshot
|
||||
import * as React from 'react';
|
||||
throw new Error('no')
|
||||
|
@ -78,29 +80,29 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
}
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127
|
||||
test('boundaries', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554152127
|
||||
test('boundaries', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.write(
|
||||
'FunctionDefault.js',
|
||||
outdent`
|
||||
await session.write(
|
||||
'FunctionDefault.js',
|
||||
outdent`
|
||||
export default function FunctionDefault() {
|
||||
return <h2>hello</h2>
|
||||
}
|
||||
`
|
||||
)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import FunctionDefault from './FunctionDefault.js'
|
||||
import * as React from 'react'
|
||||
class ErrorBoundary extends React.Component {
|
||||
|
@ -130,58 +132,58 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
}
|
||||
export default App;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h2').textContent)
|
||||
).toBe('hello')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h2').textContent)
|
||||
).toBe('hello')
|
||||
|
||||
await session.write(
|
||||
'FunctionDefault.js',
|
||||
`export default function FunctionDefault() { throw new Error('no'); }`
|
||||
)
|
||||
await session.write(
|
||||
'FunctionDefault.js',
|
||||
`export default function FunctionDefault() { throw new Error('no'); }`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h2').textContent)
|
||||
).toBe('error')
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h2').textContent)
|
||||
).toBe('error')
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// TODO: investigate why this fails when running outside of the Next.js
|
||||
// monorepo e.g. fails when using yarn create next-app
|
||||
// https://github.com/vercel/next.js/pull/23203
|
||||
test.skip('internal package errors', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// TODO: investigate why this fails when running outside of the Next.js
|
||||
// monorepo e.g. fails when using yarn create next-app
|
||||
// https://github.com/vercel/next.js/pull/23203
|
||||
test.skip('internal package errors', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// Make a react build-time error.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Make a react build-time error.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default function FunctionNamed() {
|
||||
return <div>{{}}</div>
|
||||
}`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
// We internally only check the script path, not including the line number
|
||||
// and error message because the error comes from an external library.
|
||||
// This test ensures that the errored script path is correctly resolved.
|
||||
expect(await session.getRedboxSource()).toContain(
|
||||
`../../../../packages/next/dist/pages/_document.js`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
// We internally only check the script path, not including the line number
|
||||
// and error message because the error comes from an external library.
|
||||
// This test ensures that the errored script path is correctly resolved.
|
||||
expect(await session.getRedboxSource()).toContain(
|
||||
`../../../../packages/next/dist/pages/_document.js`
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('unterminated JSX', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('unterminated JSX', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
|
@ -190,13 +192,13 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
|
@ -205,13 +207,13 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const source = await session.getRedboxSource()
|
||||
expect(next.normalizeTestDirContent(source)).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
const source = await session.getRedboxSource()
|
||||
expect(next.normalizeTestDirContent(source)).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
"./index.js
|
||||
Error:
|
||||
x Unexpected token. Did you mean \`{'}'}\` or \`}\`?
|
||||
|
@ -238,27 +240,27 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
./index.js
|
||||
./pages/index.js"
|
||||
`)
|
||||
)
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// Module trace is only available with webpack 5
|
||||
test('conversion to class component (1)', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// Module trace is only available with webpack 5
|
||||
test('conversion to class component (1)', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.write(
|
||||
'Child.js',
|
||||
outdent`
|
||||
await session.write(
|
||||
'Child.js',
|
||||
outdent`
|
||||
export default function ClickCount() {
|
||||
return <p>hello</p>
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import Child from './Child';
|
||||
|
||||
export default function Home() {
|
||||
|
@ -269,16 +271,16 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('hello')
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('hello')
|
||||
|
||||
await session.patch(
|
||||
'Child.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'Child.js',
|
||||
outdent`
|
||||
import { Component } from 'react';
|
||||
export default class ClickCount extends Component {
|
||||
render() {
|
||||
|
@ -286,14 +288,14 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'Child.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'Child.js',
|
||||
outdent`
|
||||
import { Component } from 'react';
|
||||
export default class ClickCount extends Component {
|
||||
render() {
|
||||
|
@ -301,23 +303,23 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('hello new')
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('hello new')
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('css syntax errors', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('css syntax errors', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.write('index.module.css', `.button {}`)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.write('index.module.css', `.button {}`)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import './index.module.css';
|
||||
export default () => {
|
||||
return (
|
||||
|
@ -327,35 +329,35 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
// Syntax error
|
||||
await session.patch('index.module.css', `.button {`)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatch('./index.module.css:1:1')
|
||||
expect(source).toMatch('Syntax error: ')
|
||||
expect(source).toMatch('Unclosed block')
|
||||
expect(source).toMatch('> 1 | .button {')
|
||||
expect(source).toMatch(' | ^')
|
||||
// Syntax error
|
||||
await session.patch('index.module.css', `.button {`)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
const source = await session.getRedboxSource()
|
||||
expect(source).toMatch('./index.module.css:1:1')
|
||||
expect(source).toMatch('Syntax error: ')
|
||||
expect(source).toMatch('Unclosed block')
|
||||
expect(source).toMatch('> 1 | .button {')
|
||||
expect(source).toMatch(' | ^')
|
||||
|
||||
// Not local error
|
||||
await session.patch('index.module.css', `button {}`)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
const source2 = await session.getRedboxSource()
|
||||
expect(source2).toMatchSnapshot()
|
||||
// Not local error
|
||||
await session.patch('index.module.css', `button {}`)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
const source2 = await session.getRedboxSource()
|
||||
expect(source2).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('logbox: anchors links in error messages', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('logbox: anchors links in error messages', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -369,39 +371,39 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header = await session.getRedboxDescription()
|
||||
expect(header).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(1)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header = await session.getRedboxDescription()
|
||||
expect(header).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(1)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -415,39 +417,39 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header2 = await session.getRedboxDescription()
|
||||
expect(header2).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(1)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header2 = await session.getRedboxDescription()
|
||||
expect(header2).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(1)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -461,39 +463,39 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header3 = await session.getRedboxDescription()
|
||||
expect(header3).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(1)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header3 = await session.getRedboxDescription()
|
||||
expect(header3).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(1)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -507,53 +509,53 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header4 = await session.getRedboxDescription()
|
||||
expect(header4).toMatchInlineSnapshot(
|
||||
`"Error: multiple http://nextjs.org links http://example.com"`
|
||||
)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(2)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header4 = await session.getRedboxDescription()
|
||||
expect(header4).toMatchInlineSnapshot(
|
||||
`"Error: multiple http://nextjs.org links http://example.com"`
|
||||
)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(2)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(2)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(2)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -567,59 +569,59 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header5 = await session.getRedboxDescription()
|
||||
expect(header5).toMatchInlineSnapshot(
|
||||
`"Error: multiple http://nextjs.org links (http://example.com)"`
|
||||
)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(2)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
const header5 = await session.getRedboxDescription()
|
||||
expect(header5).toMatchInlineSnapshot(
|
||||
`"Error: multiple http://nextjs.org links (http://example.com)"`
|
||||
)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(2)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
.shadowRoot.querySelectorAll('#nextjs__container_errors_desc a')
|
||||
.length
|
||||
)
|
||||
).toBe(2)
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(1)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
expect(
|
||||
await session.evaluate(
|
||||
() =>
|
||||
(
|
||||
document
|
||||
.querySelector('body > nextjs-portal')
|
||||
.shadowRoot.querySelector(
|
||||
'#nextjs__container_errors_desc a:nth-of-type(2)'
|
||||
) as any
|
||||
).href
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('non-Error errors are handled properly', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('non-Error errors are handled properly', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
throw {'a': 1, 'b': 'x'};
|
||||
return (
|
||||
|
@ -627,28 +629,28 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: {\\"a\\":1,\\"b\\":\\"x\\"}"`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: {\\"a\\":1,\\"b\\":\\"x\\"}"`
|
||||
)
|
||||
|
||||
// fix previous error
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// fix previous error
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
return (
|
||||
<div>hello</div>
|
||||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
class Hello {}
|
||||
|
||||
export default () => {
|
||||
|
@ -658,27 +660,27 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toContain(
|
||||
`Error: class Hello {`
|
||||
)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toContain(
|
||||
`Error: class Hello {`
|
||||
)
|
||||
|
||||
// fix previous error
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// fix previous error
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
return (
|
||||
<div>hello</div>
|
||||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
throw "string error"
|
||||
return (
|
||||
|
@ -686,27 +688,27 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: string error"`
|
||||
)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toMatchInlineSnapshot(
|
||||
`"Error: string error"`
|
||||
)
|
||||
|
||||
// fix previous error
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// fix previous error
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
return (
|
||||
<div>hello</div>
|
||||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
export default () => {
|
||||
throw null
|
||||
return (
|
||||
|
@ -714,12 +716,13 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toContain(
|
||||
`Error: A null error was thrown`
|
||||
)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxDescription()).toContain(
|
||||
`Error: A null error was thrown`
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,18 +5,20 @@ import { check, describeVariants as describe } from 'next-test-utils'
|
|||
import { outdent } from 'outdent'
|
||||
import path from 'path'
|
||||
|
||||
describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
describe.each(['default', 'turbo', 'experimentalTurbo'])(
|
||||
'ReactRefreshLogBox %s',
|
||||
() => {
|
||||
const { next } = nextTestSetup({
|
||||
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
test('logbox: can recover from a syntax error without losing state', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('logbox: can recover from a syntax error without losing state', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -30,23 +32,23 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
|
||||
await session.patch('index.js', `export default () => <div/`)
|
||||
await session.patch('index.js', `export default () => <div/`)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default () => <div/'
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default () => <div/'
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -60,24 +62,24 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await check(
|
||||
() => session.evaluate(() => document.querySelector('p').textContent),
|
||||
/Count: 1/
|
||||
)
|
||||
await check(
|
||||
() => session.evaluate(() => document.querySelector('p').textContent),
|
||||
/Count: 1/
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('logbox: can recover from a event handler error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('logbox: can recover from a event handler error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -94,18 +96,18 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('0')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('0')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('1')
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
|
||||
"index.js (7:10) @ eval
|
||||
|
||||
5 | const increment = useCallback(() => {
|
||||
|
@ -117,9 +119,9 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
10 | <main>"
|
||||
`)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -133,38 +135,38 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 1')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 2')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 1')
|
||||
await session.evaluate(() => document.querySelector('button').click())
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Count: 2')
|
||||
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
test('logbox: can recover from a component error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
test('logbox: can recover from a component error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
await session.write(
|
||||
'child.js',
|
||||
outdent`
|
||||
await session.write(
|
||||
'child.js',
|
||||
outdent`
|
||||
export default function Child() {
|
||||
return <p>Hello</p>;
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import Child from './child'
|
||||
|
||||
export default function Index() {
|
||||
|
@ -175,53 +177,53 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
)
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Hello')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Hello')
|
||||
|
||||
await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
// hello
|
||||
export default function Child() {
|
||||
throw new Error('oops')
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default function Child()'
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
'export default function Child()'
|
||||
)
|
||||
|
||||
const didNotReload = await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
const didNotReload = await session.patch(
|
||||
'child.js',
|
||||
outdent`
|
||||
export default function Child() {
|
||||
return <p>Hello</p>;
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(didNotReload).toBe(true)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Hello')
|
||||
expect(didNotReload).toBe(true)
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('p').textContent)
|
||||
).toBe('Hello')
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262
|
||||
test('render error not shown right after syntax error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554137262
|
||||
test('render error not shown right after syntax error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// Starting here:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Starting here:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
class ClassDefault extends React.Component {
|
||||
render() {
|
||||
|
@ -231,16 +233,16 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h1').textContent)
|
||||
).toBe('Default Export')
|
||||
expect(
|
||||
await session.evaluate(() => document.querySelector('h1').textContent)
|
||||
).toBe('Default Export')
|
||||
|
||||
// Break it with a syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Break it with a syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
class ClassDefault extends React.Component {
|
||||
|
@ -251,13 +253,13 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
// Now change the code to introduce a runtime error without fixing the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Now change the code to introduce a runtime error without fixing the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
class ClassDefault extends React.Component {
|
||||
|
@ -269,13 +271,13 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
// Now fix the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Now fix the syntax error:
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
class ClassDefault extends React.Component {
|
||||
|
@ -287,29 +289,29 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default ClassDefault;
|
||||
`
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
)
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
|
||||
await check(async () => {
|
||||
const source = await session.getRedboxSource()
|
||||
return source?.includes('render() {') ? 'success' : source
|
||||
}, 'success')
|
||||
await check(async () => {
|
||||
const source = await session.getRedboxSource()
|
||||
return source?.includes('render() {') ? 'success' : source
|
||||
}, 'success')
|
||||
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"throw new Error('nooo');"
|
||||
)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"throw new Error('nooo');"
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016
|
||||
test('stuck error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554144016
|
||||
test('stuck error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// We start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// We start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
function FunctionDefault() {
|
||||
|
@ -318,23 +320,23 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
|
||||
export default FunctionDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// We add a new file. Let's call it Foo.js.
|
||||
await session.write(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
// We add a new file. Let's call it Foo.js.
|
||||
await session.write(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
// intentionally skips export
|
||||
export default function Foo() {
|
||||
return React.createElement('h1', null, 'Foo');
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// We edit our first file to use it.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// We edit our first file to use it.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
import Foo from './Foo';
|
||||
function FunctionDefault() {
|
||||
|
@ -342,50 +344,50 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
}
|
||||
export default FunctionDefault;
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// We get an error because Foo didn't import React. Fair.
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"return React.createElement('h1', null, 'Foo');"
|
||||
)
|
||||
// We get an error because Foo didn't import React. Fair.
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(await session.getRedboxSource()).toInclude(
|
||||
"return React.createElement('h1', null, 'Foo');"
|
||||
)
|
||||
|
||||
// Let's add that to Foo.
|
||||
await session.patch(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
// Let's add that to Foo.
|
||||
await session.patch(
|
||||
'Foo.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
export default function Foo() {
|
||||
return React.createElement('h1', null, 'Foo');
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
// Expected: this fixes the problem
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
// Expected: this fixes the problem
|
||||
expect(await session.hasRedbox(false)).toBe(false)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098
|
||||
test('syntax > runtime error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/pull/3#issuecomment-554150098
|
||||
test('syntax > runtime error', async () => {
|
||||
const { session, cleanup } = await sandbox(next)
|
||||
|
||||
// Start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Start here.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
|
||||
export default function FunctionNamed() {
|
||||
return <div />
|
||||
}
|
||||
`
|
||||
)
|
||||
// TODO: this acts weird without above step
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
)
|
||||
// TODO: this acts weird without above step
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
|
@ -396,20 +398,20 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
return <div />
|
||||
}
|
||||
`
|
||||
)
|
||||
)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
if (process.platform === 'win32') {
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
} else {
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
if (process.platform === 'win32') {
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
} else {
|
||||
expect(await session.getRedboxSource()).toMatchSnapshot()
|
||||
}
|
||||
|
||||
// Make a syntax error.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
// Make a syntax error.
|
||||
await session.patch(
|
||||
'index.js',
|
||||
outdent`
|
||||
import * as React from 'react';
|
||||
let i = 0
|
||||
setInterval(() => {
|
||||
|
@ -417,14 +419,14 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
throw Error('no ' + i)
|
||||
}, 1000)
|
||||
export default function FunctionNamed() {`
|
||||
)
|
||||
)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
"./index.js
|
||||
Error:
|
||||
x Expected '}', got '<eof>'
|
||||
|
@ -443,15 +445,15 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
./index.js
|
||||
./pages/index.js"
|
||||
`)
|
||||
)
|
||||
)
|
||||
|
||||
// Test that runtime error does not take over:
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
// Test that runtime error does not take over:
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
expect(await session.hasRedbox(true)).toBe(true)
|
||||
expect(
|
||||
next.normalizeTestDirContent(await session.getRedboxSource())
|
||||
).toMatchInlineSnapshot(
|
||||
next.normalizeSnapshot(`
|
||||
"./index.js
|
||||
Error:
|
||||
x Expected '}', got '<eof>'
|
||||
|
@ -470,8 +472,9 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
|
|||
./index.js
|
||||
./pages/index.js"
|
||||
`)
|
||||
)
|
||||
)
|
||||
|
||||
await cleanup()
|
||||
})
|
||||
})
|
||||
await cleanup()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
|
@ -26,7 +26,10 @@ import type { RequestInit, Response } from 'node-fetch'
|
|||
import type { NextServer } from 'next/dist/server/next'
|
||||
import type { BrowserInterface } from './browsers/base'
|
||||
|
||||
import { shouldRunTurboDevTest } from './turbo'
|
||||
import {
|
||||
shouldRunExperimentalTurboDevTest,
|
||||
shouldRunTurboDevTest,
|
||||
} from './turbo'
|
||||
|
||||
export { shouldRunTurboDevTest }
|
||||
|
||||
|
@ -327,6 +330,7 @@ export interface NextDevOptions {
|
|||
bootupMarker?: RegExp
|
||||
nextStart?: boolean
|
||||
turbo?: boolean
|
||||
experimentalTurbo?: boolean
|
||||
|
||||
stderr?: false
|
||||
stdout?: false
|
||||
|
@ -374,12 +378,19 @@ export function runNextCommandDev(
|
|||
const bootupMarkers = {
|
||||
dev: /compiled .*successfully/i,
|
||||
turbo: /started server/i,
|
||||
experimentalTurbo: /started server/i,
|
||||
start: /started server/i,
|
||||
}
|
||||
if (
|
||||
(opts.bootupMarker && opts.bootupMarker.test(message)) ||
|
||||
bootupMarkers[
|
||||
opts.nextStart || stdOut ? 'start' : opts?.turbo ? 'turbo' : 'dev'
|
||||
opts.nextStart || stdOut
|
||||
? 'start'
|
||||
: opts?.experimentalTurbo
|
||||
? 'experimentalTurbo'
|
||||
: opts?.turbo
|
||||
? 'turbo'
|
||||
: 'dev'
|
||||
].test(message)
|
||||
) {
|
||||
if (!didResolve) {
|
||||
|
@ -434,6 +445,7 @@ export function launchApp(
|
|||
) {
|
||||
const options = opts ?? {}
|
||||
const useTurbo = shouldRunTurboDevTest()
|
||||
const useExperimentalTurbo = shouldRunExperimentalTurboDevTest()
|
||||
|
||||
return runNextCommandDev(
|
||||
[useTurbo ? '--turbo' : undefined, dir, '-p', port as string].filter(
|
||||
|
@ -443,6 +455,7 @@ export function launchApp(
|
|||
{
|
||||
...options,
|
||||
turbo: useTurbo,
|
||||
experimentalTurbo: useExperimentalTurbo,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1008,23 +1021,30 @@ export function findAllTelemetryEvents(output: string, eventName: string) {
|
|||
return events.filter((e) => e.eventName === eventName).map((e) => e.payload)
|
||||
}
|
||||
|
||||
type TestVariants = 'default' | 'turbo'
|
||||
type TestVariants = 'default' | 'turbo' | 'experimentalTurbo'
|
||||
|
||||
// WEB-168: There are some differences / incompletes in turbopack implementation enforces jest requires to update
|
||||
// test snapshot when run against turbo. This fn returns describe, or describe.skip dependes on the running context
|
||||
// to avoid force-snapshot update per each runs until turbopack update includes all the changes.
|
||||
export function getSnapshotTestDescribe(variant: TestVariants) {
|
||||
const runningEnv = variant ?? 'default'
|
||||
if (runningEnv !== 'default' && runningEnv !== 'turbo') {
|
||||
if (
|
||||
runningEnv !== 'default' &&
|
||||
runningEnv !== 'turbo' &&
|
||||
runningEnv !== 'experimentalTurbo'
|
||||
) {
|
||||
throw new Error(
|
||||
`An invalid test env was passed: ${variant} (only "default" and "turbo" are valid options)`
|
||||
`An invalid test env was passed: ${variant} (only "default", "turbo" and "experimentalTurbo" are valid options)`
|
||||
)
|
||||
}
|
||||
|
||||
const shouldRunTurboDev = shouldRunTurboDevTest()
|
||||
const shouldRunExperimentalTurboDev = shouldRunExperimentalTurboDevTest()
|
||||
const shouldSkip =
|
||||
(runningEnv === 'turbo' && !shouldRunTurboDev) ||
|
||||
(runningEnv === 'default' && shouldRunTurboDev)
|
||||
(runningEnv === 'experimentalTurbo' && !shouldRunExperimentalTurboDev) ||
|
||||
(runningEnv === 'default' &&
|
||||
(shouldRunTurboDev || shouldRunExperimentalTurboDev))
|
||||
|
||||
return shouldSkip ? describe.skip : describe
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
let loggedTurbopack = false
|
||||
let loggedExperimentalTurbopack = false
|
||||
|
||||
/**
|
||||
* Utility function to determine if a given test case needs to run with --turbo.
|
||||
|
@ -24,3 +25,28 @@ export function shouldRunTurboDevTest(): boolean {
|
|||
|
||||
return shouldRunTurboDev
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to determine if a given test case needs to run with --experimental-turbo.
|
||||
*
|
||||
* This is primarily for the gradual test enablement with latest turbopack upstream changes.
|
||||
*
|
||||
* Note: it could be possible to dynamically create test cases itself (createDevTest(): it.each([...])), but
|
||||
* it makes hard to conform with existing lint rules. Instead, starting off from manual fixture setup and
|
||||
* update test cases accordingly as turbopack changes enable more test cases.
|
||||
*/
|
||||
export function shouldRunExperimentalTurboDevTest(): boolean {
|
||||
if (!!process.env.TEST_WASM) {
|
||||
return false
|
||||
}
|
||||
|
||||
const shouldRunExperimentalTurboDev = !!process.env.EXPERIMENTAL_TURBOPACK
|
||||
if (shouldRunExperimentalTurboDev && !loggedExperimentalTurbopack) {
|
||||
require('console').log(
|
||||
`Running tests with experimental turbopack because environment variable EXPERIMENTAL_TURBOPACK is set`
|
||||
)
|
||||
loggedExperimentalTurbopack = true
|
||||
}
|
||||
|
||||
return shouldRunExperimentalTurboDev
|
||||
}
|
||||
|
|
9
test/turbopack-tests-manifest.js
Normal file
9
test/turbopack-tests-manifest.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Tests that are currently enabled with experimental Turbopack in CI.
|
||||
// Only tests that are actively testing against Turbopack should
|
||||
// be enabled here
|
||||
const enabledTests = [
|
||||
'test/development/api-cors-with-rewrite/index.test.ts',
|
||||
'test/integration/bigint/test/index.test.js',
|
||||
]
|
||||
|
||||
module.exports = { enabledTests }
|
Loading…
Reference in a new issue