Skip to content

Commit f23c292

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix-explorer-build-and-test
2 parents a977532 + 332d64a commit f23c292

2 files changed

Lines changed: 87 additions & 34 deletions

File tree

renderers/angular/src/v0_9/catalog/basic/text.component.spec.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ describe('TextComponent', () => {
6060
await fixture.whenStable();
6161
fixture.detectChanges();
6262

63-
const span = fixture.debugElement.query(By.css('span'));
64-
expect(span.nativeElement.innerHTML.trim()).toBe('<p>Hello World</p>');
63+
const element = fixture.debugElement.query(By.css('.a2ui-text'));
64+
expect(element).toBeTruthy();
65+
const p = element.query(By.css('p'));
66+
expect(p).toBeTruthy();
67+
expect(p.nativeElement.textContent.trim()).toBe('Hello World');
6568
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('Hello World');
6669
});
6770

@@ -74,7 +77,12 @@ describe('TextComponent', () => {
7477
await fixture.whenStable();
7578
fixture.detectChanges();
7679

77-
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('# Heading');
80+
expect(mockMarkdownRenderer.render).not.toHaveBeenCalled();
81+
const element = fixture.debugElement.query(By.css('.a2ui-text.h1'));
82+
expect(element).toBeTruthy();
83+
const h1 = element.query(By.css('h1'));
84+
expect(h1).toBeTruthy();
85+
expect(h1.nativeElement.textContent.trim()).toBe('Heading');
7886
});
7987

8088
it('should handle variant caption', async () => {
@@ -86,7 +94,12 @@ describe('TextComponent', () => {
8694
await fixture.whenStable();
8795
fixture.detectChanges();
8896

89-
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('*Caption*');
97+
expect(mockMarkdownRenderer.render).not.toHaveBeenCalled();
98+
const element = fixture.debugElement.query(By.css('.a2ui-text.caption'));
99+
expect(element).toBeTruthy();
100+
const em = element.query(By.css('em'));
101+
expect(em).toBeTruthy();
102+
expect(em.nativeElement.textContent.trim()).toBe('Caption');
90103
});
91104

92105
it('should handle variant h2', async () => {
@@ -98,7 +111,12 @@ describe('TextComponent', () => {
98111
await fixture.whenStable();
99112
fixture.detectChanges();
100113

101-
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('## Heading');
114+
expect(mockMarkdownRenderer.render).not.toHaveBeenCalled();
115+
const element = fixture.debugElement.query(By.css('.a2ui-text.h2'));
116+
expect(element).toBeTruthy();
117+
const h2 = element.query(By.css('h2'));
118+
expect(h2).toBeTruthy();
119+
expect(h2.nativeElement.textContent.trim()).toBe('Heading');
102120
});
103121

104122
it('should handle variant h3', async () => {
@@ -110,7 +128,12 @@ describe('TextComponent', () => {
110128
await fixture.whenStable();
111129
fixture.detectChanges();
112130

113-
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('### Heading');
131+
expect(mockMarkdownRenderer.render).not.toHaveBeenCalled();
132+
const element = fixture.debugElement.query(By.css('.a2ui-text.h3'));
133+
expect(element).toBeTruthy();
134+
const h3 = element.query(By.css('h3'));
135+
expect(h3).toBeTruthy();
136+
expect(h3.nativeElement.textContent.trim()).toBe('Heading');
114137
});
115138

116139
it('should handle variant h4', async () => {
@@ -122,7 +145,12 @@ describe('TextComponent', () => {
122145
await fixture.whenStable();
123146
fixture.detectChanges();
124147

125-
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('#### Heading');
148+
expect(mockMarkdownRenderer.render).not.toHaveBeenCalled();
149+
const element = fixture.debugElement.query(By.css('.a2ui-text.h4'));
150+
expect(element).toBeTruthy();
151+
const h4 = element.query(By.css('h4'));
152+
expect(h4).toBeTruthy();
153+
expect(h4.nativeElement.textContent.trim()).toBe('Heading');
126154
});
127155

128156
it('should handle variant h5', async () => {
@@ -134,7 +162,12 @@ describe('TextComponent', () => {
134162
await fixture.whenStable();
135163
fixture.detectChanges();
136164

137-
expect(mockMarkdownRenderer.render).toHaveBeenCalledWith('##### Heading');
165+
expect(mockMarkdownRenderer.render).not.toHaveBeenCalled();
166+
const element = fixture.debugElement.query(By.css('.a2ui-text.h5'));
167+
expect(element).toBeTruthy();
168+
const h5 = element.query(By.css('h5'));
169+
expect(h5).toBeTruthy();
170+
expect(h5.nativeElement.textContent.trim()).toBe('Heading');
138171
});
139172

140173
it('should handle missing text property', async () => {

renderers/angular/src/v0_9/catalog/basic/text.component.ts

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,35 @@ import {TextApi} from '@a2ui/web_core/v0_9/basic_catalog';
3838
@Component({
3939
selector: 'a2ui-v09-text',
4040
standalone: true,
41-
template: ` <span [class]="'a2ui-text ' + variant()" [innerHTML]="resolvedText()"> </span> `,
41+
template: `
42+
@if (isNonMarkdownVariant()) {
43+
<span [class]="'a2ui-text ' + variant()">
44+
<!-- Nesting block elements like h1 inside a span is not correct HTML5. We should refactor this-->
45+
@switch (variant()) {
46+
@case ('h1') {
47+
<h1>{{ text() }}</h1>
48+
}
49+
@case ('h2') {
50+
<h2>{{ text() }}</h2>
51+
}
52+
@case ('h3') {
53+
<h3>{{ text() }}</h3>
54+
}
55+
@case ('h4') {
56+
<h4>{{ text() }}</h4>
57+
}
58+
@case ('h5') {
59+
<h5>{{ text() }}</h5>
60+
}
61+
@case ('caption') {
62+
<em>{{ text() }}</em>
63+
}
64+
}
65+
</span>
66+
} @else {
67+
<span [class]="'a2ui-text ' + variant()" [innerHTML]="resolvedText()"></span>
68+
}
69+
`,
4270
// We use :host ::ng-deep because the template content is injected via innerHTML (Markdown).
4371
// Angular's default view encapsulation cannot target elements injected via innerHTML because they lack the scoping attributes generated at compile time.
4472
// ::ng-deep allows styles to reach into the injected HTML, while :host keeps them scoped to this component.
@@ -107,42 +135,34 @@ import {TextApi} from '@a2ui/web_core/v0_9/basic_catalog';
107135
export class TextComponent extends BasicCatalogComponent<typeof TextApi> {
108136
private markdownRenderer = inject(MarkdownRenderer);
109137

138+
private static readonly NON_MARKDOWN_VARIANTS = new Set<string>([
139+
'h1',
140+
'h2',
141+
'h3',
142+
'h4',
143+
'h5',
144+
'caption',
145+
]);
146+
110147
readonly variant = computed(() => this.props()['variant']?.value() || 'body');
111148
readonly text = computed(() => this.props()['text']?.value() || '');
112149

150+
readonly isNonMarkdownVariant = computed(() => {
151+
return TextComponent.NON_MARKDOWN_VARIANTS.has(this.variant());
152+
});
153+
113154
resolvedText = signal<string>('');
114155
private renderRequestId = 0;
115156

116157
constructor() {
117158
super();
118159
effect(() => {
119-
const text = this.text();
120-
const variant = this.variant();
121-
let value = text;
122-
123-
switch (variant) {
124-
case 'h1':
125-
value = `# ${text}`;
126-
break;
127-
case 'h2':
128-
value = `## ${text}`;
129-
break;
130-
case 'h3':
131-
value = `### ${text}`;
132-
break;
133-
case 'h4':
134-
value = `#### ${text}`;
135-
break;
136-
case 'h5':
137-
value = `##### ${text}`;
138-
break;
139-
case 'caption':
140-
value = `*${text}*`;
141-
break;
160+
if (this.isNonMarkdownVariant()) {
161+
return;
142162
}
143-
163+
const text = this.text();
144164
const requestId = ++this.renderRequestId;
145-
this.markdownRenderer.render(value).then(rendered => {
165+
this.markdownRenderer.render(text).then(rendered => {
146166
if (requestId === this.renderRequestId) {
147167
this.resolvedText.set(rendered);
148168
}

0 commit comments

Comments
 (0)