fix(aio): switch from `innerText` to `textContent` to support older browsers

`innerText` is not supported in Firefox prior to v45. In most cases (at least
the ones we are interested in), `innerText` and `textContent` work equally well,
but `textContent` is more performant (as it doesn't require a reflow).

From [MDN][1] on the differences of `innerText` vs `textContent`:

> - [...]
> - `innerText` is aware of style and will not return the text of hidden
>   elements, whereas `textContent` will.
> - As `innerText` is aware of CSS styling, it will trigger a reflow, whereas
>   `textContent` will not.
> - [...]

[1]: https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText

Fixes #17585
This commit is contained in:
Georgios Kalpakas 2017-06-18 12:29:48 +03:00 committed by Hans
parent 3093c55e9e
commit 4f37f86433
13 changed files with 32 additions and 29 deletions

View File

@ -27,7 +27,7 @@ describe('AppComponent', function () {
it('should have expected <h1> text', () => { it('should have expected <h1> text', () => {
fixture.detectChanges(); fixture.detectChanges();
const h1 = de.nativeElement; const h1 = de.nativeElement;
expect(h1.innerText).toMatch(/angular/i, expect(h1.textContent).toMatch(/angular/i,
'<h1> should say something about "Angular"'); '<h1> should say something about "Angular"');
}); });
}); });

View File

@ -111,7 +111,7 @@ export class AppComponent implements AfterViewInit, OnInit {
} }
onSave(event: KeyboardEvent) { onSave(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerText : ''; let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).textContent : '';
this.alert('Saved.' + evtMsg); this.alert('Saved.' + evtMsg);
if (event) { event.stopPropagation(); } if (event) { event.stopPropagation(); }
} }

View File

@ -398,19 +398,19 @@ describe('AppComponent', () => {
it('should display a guide page (guide/pipes)', () => { it('should display a guide page (guide/pipes)', () => {
locationService.go('guide/pipes'); locationService.go('guide/pipes');
fixture.detectChanges(); fixture.detectChanges();
expect(docViewer.innerText).toMatch(/Pipes/i); expect(docViewer.textContent).toMatch(/Pipes/i);
}); });
it('should display the api page', () => { it('should display the api page', () => {
locationService.go('api'); locationService.go('api');
fixture.detectChanges(); fixture.detectChanges();
expect(docViewer.innerText).toMatch(/API/i); expect(docViewer.textContent).toMatch(/API/i);
}); });
it('should display a marketing page', () => { it('should display a marketing page', () => {
locationService.go('features'); locationService.go('features');
fixture.detectChanges(); fixture.detectChanges();
expect(docViewer.innerText).toMatch(/Features/i); expect(docViewer.textContent).toMatch(/Features/i);
}); });
it('should update the document title', () => { it('should update the document title', () => {
@ -632,7 +632,7 @@ describe('AppComponent', () => {
describe('footer', () => { describe('footer', () => {
it('should have version number', () => { it('should have version number', () => {
const versionEl: HTMLElement = fixture.debugElement.query(By.css('aio-footer')).nativeElement; const versionEl: HTMLElement = fixture.debugElement.query(By.css('aio-footer')).nativeElement;
expect(versionEl.innerText).toContain(TestHttp.versionFull); expect(versionEl.textContent).toContain(TestHttp.versionFull);
}); });
}); });

View File

@ -62,7 +62,7 @@ describe('CodeExampleComponent', () => {
TestBed.overrideComponent(HostComponent, { TestBed.overrideComponent(HostComponent, {
set: {template: '<code-example title="Great Example"></code-example>'}}); set: {template: '<code-example title="Great Example"></code-example>'}});
createComponent(oneLineCode); createComponent(oneLineCode);
const actual = codeExampleDe.query(By.css('header')).nativeElement.innerText; const actual = codeExampleDe.query(By.css('header')).nativeElement.textContent;
expect(actual).toBe('Great Example'); expect(actual).toBe('Great Example');
}); });

View File

@ -116,7 +116,7 @@ describe('CodeComponent', () => {
hostComponent.linenums = false; hostComponent.linenums = false;
hostComponent.code = ' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n'; hostComponent.code = ' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n';
fixture.detectChanges(); fixture.detectChanges();
const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText; const codeContent = codeComponentDe.nativeElement.querySelector('code').textContent;
expect(codeContent).toEqual('abc\n let x = text.split(\'\\n\');\nghi\n\njkl'); expect(codeContent).toEqual('abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
}); });
@ -124,7 +124,7 @@ describe('CodeComponent', () => {
hostComponent.linenums = false; hostComponent.linenums = false;
hostComponent.code = '\n\n\n' + smallMultiLineCode + '\n\n\n'; hostComponent.code = '\n\n\n' + smallMultiLineCode + '\n\n\n';
fixture.detectChanges(); fixture.detectChanges();
const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText; const codeContent = codeComponentDe.nativeElement.querySelector('code').textContent;
expect(codeContent).toEqual(codeContent.trim()); expect(codeContent).toEqual(codeContent.trim());
}); });
@ -141,7 +141,7 @@ describe('CodeComponent', () => {
function getErrorMessage() { function getErrorMessage() {
const missing: HTMLElement = codeComponentDe.nativeElement.querySelector('.code-missing'); const missing: HTMLElement = codeComponentDe.nativeElement.querySelector('.code-missing');
return missing ? missing.innerText : null; return missing ? missing.textContent : null;
} }
it('should not display "code-missing" class when there is some code', () => { it('should not display "code-missing" class when there is some code', () => {

View File

@ -108,7 +108,7 @@ export class CodeComponent implements OnChanges {
if (!this.code) { if (!this.code) {
const src = this.path ? this.path + (this.region ? '#' + this.region : '') : ''; const src = this.path ? this.path + (this.region ? '#' + this.region : '') : '';
const srcMsg = src ? ` for<br>${src}` : '.'; const srcMsg = src ? ` for\n${src}` : '.';
this.setCodeHtml(`<p class="code-missing">The code sample is missing${srcMsg}</p>`); this.setCodeHtml(`<p class="code-missing">The code sample is missing${srcMsg}</p>`);
return; return;
} }
@ -129,8 +129,8 @@ export class CodeComponent implements OnChanges {
} }
doCopy() { doCopy() {
// We take the innerText because we don't want it to be HTML encoded // We take the textContent because we don't want it to be HTML encoded
const code = this.codeContainer.nativeElement.innerText; const code = this.codeContainer.nativeElement.textContent.trim();
if (this.copier.copyText(code)) { if (this.copier.copyText(code)) {
this.logger.log('Copied code to clipboard:', code); this.logger.log('Copied code to clipboard:', code);
// success snackbar alert // success snackbar alert

View File

@ -25,11 +25,11 @@ describe('CurrentLocationComponent', () => {
it('should render the current location', () => { it('should render the current location', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(element.innerText).toEqual('initial/url'); expect(element.textContent).toEqual('initial/url');
locationService.urlSubject.next('next/url'); locationService.urlSubject.next('next/url');
fixture.detectChanges(); fixture.detectChanges();
expect(element.innerText).toEqual('next/url'); expect(element.textContent).toEqual('next/url');
}); });
}); });

View File

@ -183,7 +183,7 @@ describe('LiveExampleComponent', () => {
testComponent(() => { testComponent(() => {
const expectedTitle = 'live example'; const expectedTitle = 'live example';
const anchor = getLiveExampleAnchor(); const anchor = getLiveExampleAnchor();
expect(anchor.innerText).toBe(expectedTitle, 'anchor content'); expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title'); expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
}); });
}); });
@ -193,7 +193,7 @@ describe('LiveExampleComponent', () => {
setHostTemplate(`<live-example title="${expectedTitle}"></live-example>`); setHostTemplate(`<live-example title="${expectedTitle}"></live-example>`);
testComponent(() => { testComponent(() => {
const anchor = getLiveExampleAnchor(); const anchor = getLiveExampleAnchor();
expect(anchor.innerText).toBe(expectedTitle, 'anchor content'); expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title'); expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
}); });
}); });
@ -203,7 +203,7 @@ describe('LiveExampleComponent', () => {
setHostTemplate('<live-example title="ignore this title"></live-example>'); setHostTemplate('<live-example title="ignore this title"></live-example>');
testComponent(() => { testComponent(() => {
const anchor = getLiveExampleAnchor(); const anchor = getLiveExampleAnchor();
expect(anchor.innerText).toBe(liveExampleContent, 'anchor content'); expect(anchor.textContent).toBe(liveExampleContent, 'anchor content');
expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title'); expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title');
}); });
}); });

View File

@ -83,11 +83,14 @@ describe('TocComponent', () => {
it('should only display H2 and H3 TocItems', () => { it('should only display H2 and H3 TocItems', () => {
tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C', 'h3')]); tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C', 'h3')]);
fixture.detectChanges(); fixture.detectChanges();
const items = tocComponentDe.queryAllNodes(By.css('li'));
expect(items.length).toBe(2); const tocItems = tocComponentDe.queryAllNodes(By.css('li'));
expect(items.find(item => item.nativeNode.innerText === 'Heading A')).toBeFalsy(); const textContents = tocItems.map(item => item.nativeNode.textContent.trim());
expect(items.find(item => item.nativeNode.innerText === 'Heading B')).toBeTruthy();
expect(items.find(item => item.nativeNode.innerText === 'Heading C')).toBeTruthy(); expect(tocItems.length).toBe(2);
expect(textContents.find(text => text === 'Heading A')).toBeFalsy();
expect(textContents.find(text => text === 'Heading B')).toBeTruthy();
expect(textContents.find(text => text === 'Heading C')).toBeTruthy();
expect(setPage().tocH1Heading).toBeFalsy(); expect(setPage().tocH1Heading).toBeFalsy();
}); });

View File

@ -97,7 +97,7 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
// Only create TOC for docs with an <h1> title // Only create TOC for docs with an <h1> title
// If you don't want a TOC, add "no-toc" class to <h1> // If you don't want a TOC, add "no-toc" class to <h1>
if (titleEl) { if (titleEl) {
title = titleEl.innerText.trim(); title = titleEl.textContent.trim();
if (!/(no-toc|notoc)/i.test(titleEl.className)) { if (!/(no-toc|notoc)/i.test(titleEl.className)) {
this.tocService.genToc(this.hostElement, docId); this.tocService.genToc(this.hostElement, docId);
titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>'); titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>');

View File

@ -12,7 +12,7 @@ describe('SearchResultsComponent', () => {
let searchResults: Subject<SearchResults>; let searchResults: Subject<SearchResults>;
/** Get all text from component element */ /** Get all text from component element */
function getText() { return fixture.debugElement.nativeElement.innerText; } function getText() { return fixture.debugElement.nativeElement.textContent; }
/** Get a full set of test results. "Take" what you need */ /** Get a full set of test results. "Take" what you need */
function getTestResults(take?: number) { function getTestResults(take?: number) {

View File

@ -258,10 +258,10 @@ describe('TocService', () => {
expect(tocItem.level).toEqual('h3'); expect(tocItem.level).toEqual('h3');
}); });
it('should have title which is heading\'s innerText ', () => { it('should have title which is heading\'s textContent ', () => {
const heading = headings[3]; const heading = headings[3];
const tocItem = lastTocList[3]; const tocItem = lastTocList[3];
expect(heading.innerText).toEqual(tocItem.title); expect(heading.textContent).toEqual(tocItem.title);
}); });
it('should have "SafeHtml" content which is heading\'s innerHTML ', () => { it('should have "SafeHtml" content which is heading\'s innerHTML ', () => {

View File

@ -37,7 +37,7 @@ export class TocService {
content: this.extractHeadingSafeHtml(heading), content: this.extractHeadingSafeHtml(heading),
href: `${docId}#${this.getId(heading, idMap)}`, href: `${docId}#${this.getId(heading, idMap)}`,
level: heading.tagName.toLowerCase(), level: heading.tagName.toLowerCase(),
title: heading.innerText.trim(), title: heading.textContent.trim(),
})); }));
this.tocList.next(tocList); this.tocList.next(tocList);
@ -87,7 +87,7 @@ export class TocService {
if (id) { if (id) {
addToMap(id); addToMap(id);
} else { } else {
id = h.innerText.toLowerCase().replace(/\W+/g, '-'); id = h.textContent.trim().toLowerCase().replace(/\W+/g, '-');
id = addToMap(id); id = addToMap(id);
h.id = id; h.id = id;
} }