(el = current)} />);
});
}).toErrorDev(
- 'Received the string `true` for the boolean attribute `hidden`. ' +
+ 'Received the string `true` for the boolean attribute `disabled`. ' +
'Although this works, it will not work as expected if you pass the string "false". ' +
- 'Did you mean hidden={true}?',
+ 'Did you mean disabled={true}?',
);
- expect(el.getAttribute('hidden')).toBe('');
+ expect(el.getAttribute('disabled')).toBe('');
});
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
index b90ca9efdb32e..d56a2b8896acd 100644
--- a/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMEventPropagation-test.js
@@ -124,6 +124,23 @@ describe('ReactDOMEventListener', () => {
});
});
+ it('onBeforeMatch', async () => {
+ await testNativeBubblingEvent({
+ type: 'div',
+ reactEvent: 'onBeforeMatch',
+ reactEventType: 'beforematch',
+ nativeEvent: 'beforematch',
+ dispatch(node) {
+ node.dispatchEvent(
+ new KeyboardEvent('beforematch', {
+ bubbles: true,
+ cancelable: true,
+ }),
+ );
+ },
+ });
+ });
+
it('onBlur', async () => {
await testNativeBubblingEvent({
type: 'input',
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js
index 0880f2c6b0557..4f9f9ded0a71c 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js
@@ -33,8 +33,14 @@ function initModules() {
};
}
-const {resetModules, itRenders, clientCleanRender} =
- ReactDOMServerIntegrationUtils(initModules);
+const {
+ resetModules,
+ itRenders,
+ clientCleanRender,
+ clientRenderOnServerString,
+ serverRender,
+ streamRender,
+} = ReactDOMServerIntegrationUtils(initModules);
describe('ReactDOMServerIntegration', () => {
beforeEach(() => {
@@ -113,71 +119,68 @@ describe('ReactDOMServerIntegration', () => {
describe('boolean properties', function () {
itRenders('boolean prop with true value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe('');
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe('');
});
itRenders('boolean prop with false value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe(null);
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe(null);
});
itRenders('boolean prop with self value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe('');
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe('');
});
- // this does not seem like correct behavior, since hidden="" in HTML indicates
- // that the boolean property is present. however, it is how the current code
- // behaves, so the test is included here.
itRenders('boolean prop with "" value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe(null);
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe(null);
});
// this seems like it might mask programmer error, but it's existing behavior.
itRenders('boolean prop with string value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe('');
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe('');
});
// this seems like it might mask programmer error, but it's existing behavior.
itRenders('boolean prop with array value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe('');
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe('');
});
// this seems like it might mask programmer error, but it's existing behavior.
itRenders('boolean prop with object value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe('');
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe('');
});
// this seems like it might mask programmer error, but it's existing behavior.
itRenders('boolean prop with non-zero number value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe('');
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe('');
});
// this seems like it might mask programmer error, but it's existing behavior.
itRenders('boolean prop with zero value', async render => {
- const e = await render(
);
- expect(e.getAttribute('hidden')).toBe(null);
+ const e = await render(
);
+ expect(e.getAttribute('disabled')).toBe(null);
});
itRenders('no boolean prop with null value', async render => {
- const e = await render(
);
- expect(e.hasAttribute('hidden')).toBe(false);
+ const e = await render(
);
+ expect(e.hasAttribute('disabled')).toBe(false);
});
itRenders('no boolean prop with function value', async render => {
- const e = await render(
, 1);
- expect(e.hasAttribute('hidden')).toBe(false);
+ const e = await render(
, 1);
+ expect(e.hasAttribute('disabled')).toBe(false);
});
itRenders('no boolean prop with symbol value', async render => {
- const e = await render(
, 1);
- expect(e.hasAttribute('hidden')).toBe(false);
+ const e = await render(
, 1);
+ expect(e.hasAttribute('disabled')).toBe(false);
});
});
@@ -233,6 +236,77 @@ describe('ReactDOMServerIntegration', () => {
});
});
+ describe('hidden property (combined boolean/string attribute)', function () {
+ itRenders('hidden prop with true value', async render => {
+ const e = await render(
);
+ expect(e.getAttribute('hidden')).toBe('');
+ });
+
+ itRenders('hidden prop with false value', async render => {
+ const e = await render(
);
+ expect(e.getAttribute('hidden')).toBe(null);
+ });
+
+ itRenders('hidden prop with string value', async render => {
+ const e = await render(
);
+ expect(e.getAttribute('hidden')).toBe(
+ ReactFeatureFlags.enableNewDOMProps ? 'until-found' : '',
+ );
+ });
+
+ itRenders('hidden prop with string "false" value', async render => {
+ const e = await render(
+
,
+ ReactFeatureFlags.enableNewDOMProps ? 0 : 1,
+ );
+ expect(e.getAttribute('hidden')).toBe(
+ ReactFeatureFlags.enableNewDOMProps ? 'false' : '',
+ );
+ });
+
+ itRenders('hidden prop with string "true" value', async render => {
+ const e = await render(
+
,
+ ReactFeatureFlags.enableNewDOMProps ? 0 : 1,
+ );
+ expect(e.getAttribute('hidden')).toBe(
+ ReactFeatureFlags.enableNewDOMProps ? 'true' : '',
+ );
+ });
+
+ itRenders('hidden prop with number 0 value', async render => {
+ const e = await render(
);
+ expect(e.getAttribute('hidden')).toBe(
+ ReactFeatureFlags.enableNewDOMProps ||
+ render === clientRenderOnServerString ||
+ render === serverRender ||
+ render === streamRender
+ ? '0'
+ : null,
+ );
+ });
+
+ itRenders('no hidden prop with null value', async render => {
+ const e = await render(
);
+ expect(e.hasAttribute('hidden')).toBe(false);
+ });
+
+ itRenders('no hidden prop with undefined value', async render => {
+ const e = await render(
);
+ expect(e.hasAttribute('hidden')).toBe(false);
+ });
+
+ itRenders('no hidden prop with function value', async render => {
+ const e = await render(
, 1);
+ expect(e.hasAttribute('hidden')).toBe(false);
+ });
+
+ itRenders('no hidden prop with symbol value', async render => {
+ const e = await render(
, 1);
+ expect(e.hasAttribute('hidden')).toBe(false);
+ });
+ });
+
describe('className property', function () {
itRenders('className prop with string value', async render => {
const e = await render(
);
diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js
index 39a187ad42b58..282c5c239ef43 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtils.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtils.js
@@ -627,6 +627,7 @@ function makeSimulator(eventType) {
// A one-time snapshot with no plans to update. We'll probably want to deprecate Simulate API.
const simulatedEventTypes = [
+ 'beforematch',
'blur',
'cancel',
'click',
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 2d48410460b87..0e507e814f1b5 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -195,6 +195,12 @@ export const disableStringRefs = __NEXT_MAJOR__;
// Warn on any usage of ReactTestRenderer
export const enableReactTestRendererWarning = false;
+/**
+ * Catch-all for all changes to how we treat new HTML attributes.
+ * When attributes are newly recognized by React, or change their type, the rendered output changes.
+ */
+export const enableNewDOMProps = __NEXT_MAJOR__;
+
// -----------------------------------------------------------------------------
// Chopping Block
//
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index afc64995ee2c0..c9605ba4d8b79 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -107,5 +107,7 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = false;
+export const enableNewDOMProps = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index dcc8fd7b95f24..d6054eb74b574 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -98,5 +98,7 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = false;
+export const enableNewDOMProps = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index e82f84389e544..9d600cee709e8 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -101,6 +101,7 @@ export const enableRefAsProp = __NEXT_MAJOR__;
export const disableStringRefs = __NEXT_MAJOR__;
export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = __NEXT_MAJOR__;
+export const enableNewDOMProps = __NEXT_MAJOR__;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
index 642f2c3f08362..0bc6b26bf0f9a 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
@@ -94,5 +94,7 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = false;
+export const enableNewDOMProps = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index b276f804cc6bf..9a48a693081d9 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -97,5 +97,7 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = false;
+export const enableNewDOMProps = false;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index d8df7644fca52..d3a15a5216c11 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -122,6 +122,8 @@ export const enableReactTestRendererWarning = false;
export const enableBigIntSupport = false;
+export const enableNewDOMProps = false;
+
// TODO: Roll out with GK. Don't keep as dynamic flag for too long, though,
// because JSX is an extremely hot path.
export const disableStringRefs = false;