Skip to content

fix: add meta for TextBase and FormattedString #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions apps/nativescript-demo-ng/src/tests/spans.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Component, ElementRef, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { NativeScriptModule } from '@nativescript/angular';
import { TextBase } from '@nativescript/core';

const configureComponents = (textBaseElementName: string) => {
class BaseComponent {
@ViewChild('textBase', { static: true }) textBase: ElementRef<TextBase>;
}

@Component({
template: `<${textBaseElementName} #textBase>
<Span text="0"></Span>
<Span text="1"></Span>
<Span text="2"></Span>
</${textBaseElementName}>`,
})
class SpansComponent extends BaseComponent {}

@Component({
template: `<${textBaseElementName} #textBase>
<FormattedString>
<Span text="0"></Span>
<Span text="1"></Span>
<Span text="2"></Span>
</FormattedString>
</${textBaseElementName}>`,
})
class FormattedStringComponent extends BaseComponent {}

@Component({
template: `<${textBaseElementName} #textBase>
<Span text="0"></Span>
<Span *ngIf="show" text="1"></Span>
<Span text="2"></Span>
</${textBaseElementName}>`,
})
class DynamicSpansComponent extends BaseComponent {
show = true;
}

@Component({
template: `<${textBaseElementName} #textBase>
<FormattedString>
<Span text="0"></Span>
<Span *ngIf="show" text="1"></Span>
<Span text="2"></Span>
</FormattedString>
</${textBaseElementName}>`,
})
class DynamicFormattedStringComponent extends BaseComponent {
show = true;
}
return {
SpansComponent,
DynamicSpansComponent,
FormattedStringComponent,
DynamicFormattedStringComponent,
};
};

describe('Spans', () => {
const componentsToTest = ['Label', 'TextField', 'TextView', 'Button'];
for (const textBaseElementName of componentsToTest) {
describe(`on ${textBaseElementName}`, () => {
const { SpansComponent, DynamicSpansComponent, FormattedStringComponent, DynamicFormattedStringComponent } = configureComponents(textBaseElementName);
beforeEach(() => {
return TestBed.configureTestingModule({
declarations: [SpansComponent, DynamicSpansComponent, FormattedStringComponent, DynamicFormattedStringComponent],
imports: [NativeScriptModule],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
});
it('correctly adds', async () => {
const fixture = TestBed.createComponent(SpansComponent);
fixture.detectChanges();
const textBase = fixture.componentInstance.textBase.nativeElement;
expect(textBase.formattedText.spans.length).toBe(3);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('1');
expect(textBase.formattedText.spans.getItem(2).text).toBe('2');
});
it('correctly adds dynamically', async () => {
const fixture = TestBed.createComponent(DynamicSpansComponent);
const textBase = fixture.componentInstance.textBase.nativeElement;
fixture.detectChanges();
expect(textBase.formattedText.spans.length).toBe(3);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('1');
expect(textBase.formattedText.spans.getItem(2).text).toBe('2');
fixture.componentInstance.show = false;
fixture.detectChanges();
expect(textBase.formattedText.spans.length).toBe(2);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('2');
fixture.componentInstance.show = true;
fixture.detectChanges();
expect(textBase.formattedText.spans.length).toBe(3);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('1');
expect(textBase.formattedText.spans.getItem(2).text).toBe('2');
});

it('correctly adds FormattedString', async () => {
const fixture = TestBed.createComponent(FormattedStringComponent);
fixture.detectChanges();
const textBase = fixture.componentInstance.textBase.nativeElement;
expect(textBase.formattedText.spans.length).toBe(3);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('1');
expect(textBase.formattedText.spans.getItem(2).text).toBe('2');
});

it('correctly adds FormattedString dynamically', async () => {
const fixture = TestBed.createComponent(DynamicFormattedStringComponent);
const textBase = fixture.componentInstance.textBase.nativeElement;
fixture.detectChanges();
expect(textBase.formattedText.spans.length).toBe(3);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('1');
expect(textBase.formattedText.spans.getItem(2).text).toBe('2');
fixture.componentInstance.show = false;
fixture.detectChanges();
expect(textBase.formattedText.spans.length).toBe(2);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('2');
fixture.componentInstance.show = true;
fixture.detectChanges();
expect(textBase.formattedText.spans.length).toBe(3);
expect(textBase.formattedText.spans.getItem(0).text).toBe('0');
expect(textBase.formattedText.spans.getItem(1).text).toBe('1');
expect(textBase.formattedText.spans.getItem(2).text).toBe('2');
});
});
}
});
53 changes: 53 additions & 0 deletions apps/nativescript-demo-ng/src/tests/textnode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Component, ElementRef, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { TextNode } from '@nativescript/angular';
import { TextBase } from '@nativescript/core';

@Component({
template: `<Label #textElement>{{ text }}</Label>`,
schemas: [NO_ERRORS_SCHEMA],
standalone: true,
})
class TextNodeComponent {
@ViewChild('textElement', { static: true }) textElement: ElementRef<TextBase>;
text = 'textnode';
}

@Component({
template: `<Label #textElement
><Span>{{ text }}</Span></Label
>`,
schemas: [NO_ERRORS_SCHEMA],
standalone: true,
})
class TextNodeSpansComponent {
@ViewChild('textElement', { static: true }) textElement: ElementRef<TextBase>;
text = 'textnode';
}

describe('TextNode', () => {
beforeEach(() => TestBed.configureTestingModule({ imports: [TextNodeComponent, TextNodeSpansComponent] }));
it('should create a text node', () => {
const textNode = new TextNode('foo');
expect(textNode.text).toBe('foo');
});
it('should set text to Label', () => {
const fixture = TestBed.createComponent(TextNodeComponent);
fixture.detectChanges();
const label = fixture.componentInstance.textElement.nativeElement;
expect(label.text).toBe('textnode');
fixture.componentInstance.text = null;
fixture.detectChanges();
expect(label.text).toBe('');
});

it('should set text to Label with Spans', () => {
const fixture = TestBed.createComponent(TextNodeSpansComponent);
fixture.detectChanges();
const label = fixture.componentInstance.textElement.nativeElement;
expect(label.text).toBe('textnode');
fixture.componentInstance.text = null;
fixture.detectChanges();
expect(label.text).toBe('');
});
});
12 changes: 6 additions & 6 deletions packages/angular/src/lib/element-registry/common-views.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AbsoluteLayout, ActivityIndicator, Button, ContentView, DatePicker, DockLayout, FlexboxLayout, FormattedString, Frame, GridLayout, HtmlView, Image, Label, ListPicker, ListView, Page, Placeholder, Progress, ProxyViewContainer, Repeater, RootLayout, ScrollView, SearchBar, SegmentedBar, SegmentedBarItem, Slider, Span, StackLayout, Switch, TabView, TextField, TextView, TimePicker, WebView, WrapLayout } from '@nativescript/core';
import { frameMeta } from './metas';
import { formattedStringMeta, frameMeta, textBaseMeta } from './metas';
import { registerElement } from './registry';

// Register default NativeScript components
Expand All @@ -9,7 +9,7 @@ export function registerNativeScriptViewComponents() {
(<any>global).__ngRegisteredViews = true;
registerElement('AbsoluteLayout', () => AbsoluteLayout);
registerElement('ActivityIndicator', () => ActivityIndicator);
registerElement('Button', () => Button);
registerElement('Button', () => Button, textBaseMeta);
registerElement('ContentView', () => ContentView);
registerElement('DatePicker', () => DatePicker);
registerElement('DockLayout', () => DockLayout);
Expand All @@ -19,7 +19,7 @@ export function registerNativeScriptViewComponents() {
registerElement('Image', () => Image);
// Parse5 changes <Image> tags to <img>. WTF!
registerElement('img', () => Image);
registerElement('Label', () => Label);
registerElement('Label', () => Label, textBaseMeta);
registerElement('ListPicker', () => ListPicker);
registerElement('ListView', () => ListView);
registerElement('Page', () => Page);
Expand All @@ -37,12 +37,12 @@ export function registerNativeScriptViewComponents() {
registerElement('FlexboxLayout', () => FlexboxLayout);
registerElement('Switch', () => Switch);
registerElement('TabView', () => TabView);
registerElement('TextField', () => TextField);
registerElement('TextView', () => TextView);
registerElement('TextField', () => TextField, textBaseMeta);
registerElement('TextView', () => TextView, textBaseMeta);
registerElement('TimePicker', () => TimePicker);
registerElement('WebView', () => WebView);
registerElement('WrapLayout', () => WrapLayout);
registerElement('FormattedString', () => FormattedString);
registerElement('FormattedString', () => FormattedString, formattedStringMeta);
registerElement('Span', () => Span);
}
}
42 changes: 40 additions & 2 deletions packages/angular/src/lib/element-registry/metas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Frame, Page } from '@nativescript/core';
import { NgView, ViewClassMeta } from '../views/view-types';
import { FormattedString, Frame, Page, Span, TextBase } from '@nativescript/core';
import { isInvisibleNode } from '../views/utils';
import { NgView, ViewClassMeta } from '../views/view-types';

export const frameMeta: ViewClassMeta = {
insertChild: (parent: Frame, child: NgView) => {
Expand All @@ -14,3 +14,41 @@ export const frameMeta: ViewClassMeta = {
}
},
};

export const formattedStringMeta: ViewClassMeta = {
insertChild(parent: FormattedString, child: Span, next: Span) {
const index = parent.spans.indexOf(next);
if (index > -1) {
parent.spans.splice(index, 0, child);
} else {
parent.spans.push(child);
}
},
removeChild(parent: FormattedString, child: Span) {
const index = parent.spans.indexOf(child);
if (index > -1) {
parent.spans.splice(index, 1);
}
},
};

export const textBaseMeta: ViewClassMeta = {
insertChild(parent: TextBase, child, next) {
if (child instanceof FormattedString) {
parent.formattedText = child;
} else if (child instanceof Span) {
parent.formattedText ??= new FormattedString();
formattedStringMeta.insertChild(parent.formattedText, child, next);
}
},
removeChild(parent: TextBase, child: NgView) {
if (!parent.formattedText) return;
if (child instanceof FormattedString) {
if (parent.formattedText === child) {
parent.formattedText = null;
}
} else if (child instanceof Span) {
formattedStringMeta.removeChild(parent.formattedText, child);
}
},
};
17 changes: 9 additions & 8 deletions packages/angular/src/lib/view-util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { View, unsetValue, Placeholder, ContentView, LayoutBase, ProxyViewContainer } from '@nativescript/core';
import { unsetValue, View } from '@nativescript/core';
import { getViewClass, getViewMeta, isKnownView } from './element-registry';
import { CommentNode, NgView, TextNode, ViewExtensions, isDetachedElement, isInvisibleNode, isView, isContentView, isLayout } from './views';
import { NamespaceFilter } from './property-filter';
import { CommentNode, isContentView, isDetachedElement, isInvisibleNode, isLayout, isView, NgView, TextNode } from './views';

import { NativeScriptDebug } from './trace';
import { NgLayoutBase } from './views/view-types';
Expand Down Expand Up @@ -61,6 +61,7 @@ function printSiblingsTree(view: NgView) {
console.log(`${view} previousSiblings: ${previousSiblings} nextSiblings: ${nextSiblings}`);
}

// eslint-disable-next-line @typescript-eslint/ban-types
const propertyMaps: Map<Function, Map<string, string>> = new Map<Function, Map<string, string>>();

export class ViewUtil {
Expand Down Expand Up @@ -371,10 +372,10 @@ export class ViewUtil {
}

private ensureNgViewExtensions(view: View): NgView {
if (view.hasOwnProperty('meta')) {
if (Object.hasOwnProperty.call(view, 'meta')) {
return view as NgView;
} else {
const name = view.cssType;
const name = view.cssType || view.typeName;
const ngView = this.setNgViewExtensions(view, name);

return ngView;
Expand Down Expand Up @@ -501,8 +502,8 @@ export class ViewUtil {
}

if (!propertyMaps.has(type)) {
let propMap = new Map<string, string>();
for (let propName in instance) {
const propMap = new Map<string, string>();
for (const propName in instance) {
// tslint:disable:forin
propMap.set(propName.toLowerCase(), propName);
}
Expand Down Expand Up @@ -532,14 +533,14 @@ export class ViewUtil {
}

private setClasses(view: NgView, classesValue: string): void {
let classes = classesValue.split(whiteSpaceSplitter);
const classes = classesValue.split(whiteSpaceSplitter);
this.cssClasses(view).clear();
classes.forEach((className) => this.cssClasses(view).set(className, true));
this.syncClasses(view);
}

private syncClasses(view: NgView): void {
let classValue = (<any>Array).from(this.cssClasses(view).keys()).join(' ');
const classValue = (<any>Array).from(this.cssClasses(view).keys()).join(' ');
view.className = classValue;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/angular/src/lib/views/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContentView, LayoutBase, ProxyViewContainer, View } from '@nativescript/core';
import { ContentView, LayoutBase, ProxyViewContainer, View, ViewBase } from '@nativescript/core';
import { InvisibleNode } from './invisible-nodes';
import type { NgContentView, NgLayoutBase, NgView } from './view-types';

Expand Down