Skip to content
Permalink
Browse files

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

…rowsers

`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
  • Loading branch information...
gkalpak authored and hansl committed Jun 18, 2017
1 parent 3093c55 commit 4f37f86433dc9abe5d1932bcce93ff0dcd857973
@@ -27,7 +27,7 @@ describe('AppComponent', function () {
it('should have expected <h1> text', () => {
fixture.detectChanges();
const h1 = de.nativeElement;
expect(h1.innerText).toMatch(/angular/i,
expect(h1.textContent).toMatch(/angular/i,
'<h1> should say something about "Angular"');
});
});
@@ -111,7 +111,7 @@ export class AppComponent implements AfterViewInit, OnInit {
}

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);
if (event) { event.stopPropagation(); }
}
@@ -398,19 +398,19 @@ describe('AppComponent', () => {
it('should display a guide page (guide/pipes)', () => {
locationService.go('guide/pipes');
fixture.detectChanges();
expect(docViewer.innerText).toMatch(/Pipes/i);
expect(docViewer.textContent).toMatch(/Pipes/i);
});

it('should display the api page', () => {
locationService.go('api');
fixture.detectChanges();
expect(docViewer.innerText).toMatch(/API/i);
expect(docViewer.textContent).toMatch(/API/i);
});

it('should display a marketing page', () => {
locationService.go('features');
fixture.detectChanges();
expect(docViewer.innerText).toMatch(/Features/i);
expect(docViewer.textContent).toMatch(/Features/i);
});

it('should update the document title', () => {
@@ -632,7 +632,7 @@ describe('AppComponent', () => {
describe('footer', () => {
it('should have version number', () => {
const versionEl: HTMLElement = fixture.debugElement.query(By.css('aio-footer')).nativeElement;
expect(versionEl.innerText).toContain(TestHttp.versionFull);
expect(versionEl.textContent).toContain(TestHttp.versionFull);
});
});

@@ -62,7 +62,7 @@ describe('CodeExampleComponent', () => {
TestBed.overrideComponent(HostComponent, {
set: {template: '<code-example title="Great Example"></code-example>'}});
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');
});

@@ -116,15 +116,15 @@ describe('CodeComponent', () => {
hostComponent.linenums = false;
hostComponent.code = ' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n';
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');
});

it('should trim whitespace from the code before rendering', () => {
hostComponent.linenums = false;
hostComponent.code = '\n\n\n' + smallMultiLineCode + '\n\n\n';
fixture.detectChanges();
const codeContent = codeComponentDe.nativeElement.querySelector('code').innerText;
const codeContent = codeComponentDe.nativeElement.querySelector('code').textContent;
expect(codeContent).toEqual(codeContent.trim());
});

@@ -141,7 +141,7 @@ describe('CodeComponent', () => {

function getErrorMessage() {
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', () => {
@@ -108,7 +108,7 @@ export class CodeComponent implements OnChanges {

if (!this.code) {
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>`);
return;
}
@@ -129,8 +129,8 @@ export class CodeComponent implements OnChanges {
}

doCopy() {
// We take the innerText because we don't want it to be HTML encoded
const code = this.codeContainer.nativeElement.innerText;
// We take the textContent because we don't want it to be HTML encoded
const code = this.codeContainer.nativeElement.textContent.trim();
if (this.copier.copyText(code)) {
this.logger.log('Copied code to clipboard:', code);
// success snackbar alert
@@ -25,11 +25,11 @@ describe('CurrentLocationComponent', () => {

it('should render the current location', () => {
fixture.detectChanges();
expect(element.innerText).toEqual('initial/url');
expect(element.textContent).toEqual('initial/url');

locationService.urlSubject.next('next/url');

fixture.detectChanges();
expect(element.innerText).toEqual('next/url');
expect(element.textContent).toEqual('next/url');
});
});
@@ -183,7 +183,7 @@ describe('LiveExampleComponent', () => {
testComponent(() => {
const expectedTitle = 'live example';
const anchor = getLiveExampleAnchor();
expect(anchor.innerText).toBe(expectedTitle, 'anchor content');
expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
});
});
@@ -193,7 +193,7 @@ describe('LiveExampleComponent', () => {
setHostTemplate(`<live-example title="${expectedTitle}"></live-example>`);
testComponent(() => {
const anchor = getLiveExampleAnchor();
expect(anchor.innerText).toBe(expectedTitle, 'anchor content');
expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
});
});
@@ -203,7 +203,7 @@ describe('LiveExampleComponent', () => {
setHostTemplate('<live-example title="ignore this title"></live-example>');
testComponent(() => {
const anchor = getLiveExampleAnchor();
expect(anchor.innerText).toBe(liveExampleContent, 'anchor content');
expect(anchor.textContent).toBe(liveExampleContent, 'anchor content');
expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title');
});
});
@@ -83,11 +83,14 @@ describe('TocComponent', () => {
it('should only display H2 and H3 TocItems', () => {
tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C', 'h3')]);
fixture.detectChanges();
const items = tocComponentDe.queryAllNodes(By.css('li'));
expect(items.length).toBe(2);
expect(items.find(item => item.nativeNode.innerText === 'Heading A')).toBeFalsy();
expect(items.find(item => item.nativeNode.innerText === 'Heading B')).toBeTruthy();
expect(items.find(item => item.nativeNode.innerText === 'Heading C')).toBeTruthy();

const tocItems = tocComponentDe.queryAllNodes(By.css('li'));
const textContents = tocItems.map(item => item.nativeNode.textContent.trim());

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();
});

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

/** 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 */
function getTestResults(take?: number) {
@@ -258,10 +258,10 @@ describe('TocService', () => {
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 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 ', () => {
@@ -37,7 +37,7 @@ export class TocService {
content: this.extractHeadingSafeHtml(heading),
href: `${docId}#${this.getId(heading, idMap)}`,
level: heading.tagName.toLowerCase(),
title: heading.innerText.trim(),
title: heading.textContent.trim(),
}));

this.tocList.next(tocList);
@@ -87,7 +87,7 @@ export class TocService {
if (id) {
addToMap(id);
} else {
id = h.innerText.toLowerCase().replace(/\W+/g, '-');
id = h.textContent.trim().toLowerCase().replace(/\W+/g, '-');
id = addToMap(id);
h.id = id;
}

0 comments on commit 4f37f86

Please sign in to comment.
You can’t perform that action at this time.