Skip to content

Commit 82e05e0

Browse files
committed
Allow uppercase letters in custom attributes. Address associated edge cases
1 parent facfa87 commit 82e05e0

File tree

5 files changed

+58
-49
lines changed

5 files changed

+58
-49
lines changed

src/renderers/dom/fiber/ReactDOMFiberComponent.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,7 @@ var ReactDOMFiberComponent = {
922922
var extraAttributeNames: Set<string> = new Set();
923923
var attributes = domElement.attributes;
924924
for (var i = 0; i < attributes.length; i++) {
925-
// TODO: Do we need to lower case this to get case insensitive matches?
926-
var name = attributes[i].name;
925+
var name = attributes[i].name.toLowerCase();
927926
switch (name) {
928927
// Built-in attributes are whitelisted
929928
// TODO: Once these are gone from the server renderer, we don't need

src/renderers/dom/shared/DOMProperty.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@ var DOMPropertyInjection = {
119119
if (DOMAttributeNames.hasOwnProperty(propName)) {
120120
var attributeName = DOMAttributeNames[propName];
121121

122-
DOMProperty.aliases[attributeName] = true;
122+
DOMProperty.aliases[attributeName.toLowerCase()] = true;
123+
124+
if (lowerCased !== attributeName) {
125+
DOMProperty.aliases[lowerCased] = true;
126+
}
123127

124128
propertyInfo.attributeName = attributeName;
125129
if (__DEV__) {
@@ -238,10 +242,6 @@ var DOMProperty = {
238242
return true;
239243
}
240244

241-
if (name.toLowerCase() !== name) {
242-
return false;
243-
}
244-
245245
switch (typeof value) {
246246
case 'undefined':
247247
case 'boolean':

src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,30 +1992,14 @@ describe('ReactDOMComponent', () => {
19921992
expect(el.getAttribute('ajaxify')).toBe('ajaxy');
19931993
});
19941994

1995-
it('only allows lower-case data attributes', function() {
1996-
spyOn(console, 'error');
1997-
1995+
it('allows cased data attributes', function() {
19981996
var el = ReactTestUtils.renderIntoDocument(<div data-fooBar="true" />);
1999-
2000-
expect(el.hasAttribute('data-foobar')).toBe(false);
2001-
2002-
expectDev(console.error.calls.argsFor(0)[0]).toContain(
2003-
'Warning: Invalid DOM property `data-fooBar`. Custom attributes ' +
2004-
'and data attributes must be lower case. Instead use `data-foobar`',
2005-
);
1997+
expect(el.getAttribute('data-foobar')).toBe('true');
20061998
});
20071999

2008-
it('only allows lower-case custom attributes', function() {
2009-
spyOn(console, 'error');
2010-
2000+
it('allows cased custom attributes', function() {
20112001
var el = ReactTestUtils.renderIntoDocument(<div fooBar="true" />);
2012-
2013-
expect(el.hasAttribute('foobar')).toBe(false);
2014-
2015-
expectDev(console.error.calls.argsFor(0)[0]).toContain(
2016-
'Warning: Invalid DOM property `fooBar`. Custom attributes ' +
2017-
'and data attributes must be lower case. Instead use `foobar`',
2018-
);
2002+
expect(el.getAttribute('foobar')).toBe('true');
20192003
});
20202004
});
20212005
});

src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,12 @@ describe('ReactDOMServerIntegration', () => {
615615
expect(e.hasAttribute('className')).toBe(false);
616616
});
617617

618+
itRenders('no classname prop', async render => {
619+
const e = await render(<div classname="test" />, 1);
620+
expect(e.hasAttribute('class')).toBe(false);
621+
expect(e.hasAttribute('classname')).toBe(false);
622+
});
623+
618624
itRenders('no className prop when given the alias', async render => {
619625
const e = await render(<div class="test" />, 1);
620626
expect(e.className).toBe('');
@@ -793,9 +799,35 @@ describe('ReactDOMServerIntegration', () => {
793799
});
794800
});
795801

802+
describe('cased attributes', function() {
803+
itRenders('no badly cased aliased HTML attribute', async render => {
804+
const e = await render(<meta httpequiv="refresh" />, 1);
805+
expect(e.hasAttribute('http-equiv')).toBe(false);
806+
expect(e.hasAttribute('httpequiv')).toBe(false);
807+
});
808+
809+
itRenders('no badly cased SVG attribute', async render => {
810+
const e = await render(<text textlength="10" />, 1);
811+
expect(e.hasAttribute('textLength')).toBe(false);
812+
});
813+
814+
itRenders('no badly cased aliased SVG attribute', async render => {
815+
const e = await render(<text strokedasharray="10 10" />, 1);
816+
expect(e.hasAttribute('strokedasharray')).toBe(false);
817+
});
818+
819+
itRenders(
820+
'no badly cased original SVG attribute that is aliased',
821+
async render => {
822+
const e = await render(<text stroke-dasharray="10 10" />, 1);
823+
expect(e.hasAttribute('stroke-dasharray')).toBe(false);
824+
},
825+
);
826+
});
827+
796828
describe('unknown attributes', function() {
797829
itRenders('unknown attributes', async render => {
798-
const e = await render(<div foo="bar" />, 0);
830+
const e = await render(<div foo="bar" />);
799831
expect(e.getAttribute('foo')).toBe('bar');
800832
});
801833

@@ -809,13 +841,21 @@ describe('ReactDOMServerIntegration', () => {
809841
expect(e.hasAttribute('data-foo')).toBe(false);
810842
});
811843

812-
itRenders('no unknown data- attributes with casing', async render => {
813-
const e = await render(<div data-fooBar={null} />, 1);
814-
expect(e.hasAttribute('data-foobar')).toBe(false);
844+
itRenders('unknown data- attributes with casing', async render => {
845+
const e = await render(<div data-fooBar="true" />);
846+
expect(e.getAttribute('data-fooBar')).toBe('true');
815847
});
816848

849+
itRenders(
850+
'no unknown data- attributes with casing and null value',
851+
async render => {
852+
const e = await render(<div data-fooBar={null} />);
853+
expect(e.hasAttribute('data-fooBar')).toBe(false);
854+
},
855+
);
856+
817857
itRenders('custom attributes for non-standard elements', async render => {
818-
const e = await render(<nonstandard foo="bar" />, 0);
858+
const e = await render(<nonstandard foo="bar" />);
819859
expect(e.getAttribute('foo')).toBe('bar');
820860
});
821861

@@ -848,9 +888,9 @@ describe('ReactDOMServerIntegration', () => {
848888
},
849889
);
850890

851-
itRenders('no cased custom attributes', async render => {
852-
const e = await render(<div fooBar="test" />, 1);
853-
expect(e.hasAttribute('fooBar')).toBe(false);
891+
itRenders('cased custom attributes', async render => {
892+
const e = await render(<div fooBar="test" />);
893+
expect(e.getAttribute('fooBar')).toBe('test');
854894
});
855895
});
856896

src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,6 @@ if (__DEV__) {
101101
return true;
102102
}
103103

104-
// Otherwise, we have a custom attribute. Custom attributes should always
105-
// be lowercase.
106-
if (lowerCasedName !== name) {
107-
warning(
108-
false,
109-
'Invalid DOM property `%s`. Custom attributes and data attributes ' +
110-
'must be lower case. Instead use `%s`.%s',
111-
name,
112-
lowerCasedName,
113-
getStackAddendum(debugID),
114-
);
115-
return true;
116-
}
117-
118104
return DOMProperty.shouldSetAttribute(name, value);
119105
};
120106
}

0 commit comments

Comments
 (0)