Skip to content

Commit 8142e00

Browse files
authored
Add Angular explorer integration tests (a2ui-project#1380)
This commit adds a new suite of integration tests for the Angular explorer sample app, exercising the full pipeline from a JSON A2UI payload to the rendered DOM.
1 parent 7cdab43 commit 8142e00

42 files changed

Lines changed: 2350 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ng_build_and_test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
runs-on: ubuntu-latest
2929
strategy:
3030
matrix:
31-
task: [test, restaurant, rizzcharts, orchestrator]
31+
task: [test, restaurant, rizzcharts, orchestrator, explorer]
3232

3333
steps:
3434
- uses: actions/checkout@v6
@@ -58,6 +58,9 @@ jobs:
5858
test)
5959
cd ./renderers/angular && npm run test:ci
6060
;;
61+
explorer)
62+
cd ./renderers/angular && npx ng test a2ui_explorer --watch=false --browsers=ChromeHeadless
63+
;;
6164
restaurant)
6265
cd ./samples/client/angular && npm run build restaurant
6366
;;

renderers/angular/a2ui_explorer/src/app/app.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
17+
import {TestBed} from '@angular/core/testing';
1818
import {App} from './app';
19+
import {provideMarkdownRenderer} from '../../../src/v0_9/core/markdown';
1920

2021
describe('App', () => {
2122
beforeEach(async () => {
2223
await TestBed.configureTestingModule({
2324
imports: [App],
25+
providers: [provideMarkdownRenderer()],
2426
}).compileComponents();
2527
});
2628

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {ComponentFixture} from '@angular/core/testing';
18+
import {DemoComponent} from '../demo.component';
19+
import {getCanvas, loadExample} from './test_utils';
20+
21+
describe('Example: Flight Status', () => {
22+
let fixture: ComponentFixture<DemoComponent>;
23+
let textContent: string;
24+
25+
beforeEach(async () => {
26+
fixture = await loadExample('Flight Status');
27+
textContent = getCanvas().textContent;
28+
});
29+
30+
it('should render flight details', async () => {
31+
expect(textContent).toContain('OS 87');
32+
expect(textContent).toContain('Vienna');
33+
expect(textContent).toContain('→');
34+
expect(textContent).toContain('New York');
35+
});
36+
37+
it('should render labels', async () => {
38+
expect(textContent).toContain('Departs');
39+
expect(textContent).toContain('Arrives');
40+
expect(textContent).toContain('Status');
41+
expect(textContent).toContain('On Time');
42+
});
43+
44+
it('should render icon', async () => {
45+
const iconInnerEl = fixture.nativeElement.querySelector('.a2ui-icon');
46+
expect(iconInnerEl).toBeTruthy();
47+
expect(iconInnerEl.textContent.trim()).toBe('send');
48+
});
49+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {ComponentFixture} from '@angular/core/testing';
18+
import {DemoComponent} from '../demo.component';
19+
import {getCanvas, loadExample, wait} from './test_utils';
20+
21+
describe('Example: Email Compose', () => {
22+
let fixture: ComponentFixture<DemoComponent>;
23+
let component: DemoComponent;
24+
let textContent: string;
25+
26+
beforeEach(async () => {
27+
fixture = await loadExample('Email Compose');
28+
component = fixture.componentInstance;
29+
textContent = getCanvas().textContent;
30+
});
31+
32+
it('should render text content', async () => {
33+
expect(textContent).toContain(`FROM`);
34+
expect(textContent).toContain(`TO`);
35+
expect(textContent).toContain(`SUBJECT`);
36+
expect(textContent).toContain(`Send email`);
37+
expect(textContent).toContain(`Discard`);
38+
expect(textContent).toContain(`alex@acme.com`);
39+
expect(textContent).toContain(`jordan@acme.com`);
40+
expect(textContent).toContain(`Q4 Revenue Forecast`);
41+
expect(textContent).toContain(`Hi Jordan,`);
42+
expect(textContent).toContain(
43+
`Following up on our call. Please review the attached Q4 forecast and let me know if you have questions before the board meeting.`,
44+
);
45+
expect(textContent).toContain(`Best,`);
46+
expect(textContent).toContain(`Alex`);
47+
});
48+
49+
it('should handle Send button click', async () => {
50+
const buttons = [
51+
...fixture.nativeElement.querySelectorAll('.a2ui-button'),
52+
] as HTMLButtonElement[];
53+
const sendBtn = buttons.find(b => b.textContent.includes('Send'))!;
54+
expect(sendBtn).withContext('Should find Send button').toBeTruthy();
55+
56+
sendBtn.click();
57+
fixture.detectChanges();
58+
await wait(50);
59+
fixture.detectChanges();
60+
61+
expect(component.eventsLog.length).toBe(1);
62+
expect(component.eventsLog[0].action.name).toBe('send');
63+
});
64+
65+
it('should handle Discard button click', async () => {
66+
const buttons = [
67+
...fixture.nativeElement.querySelectorAll('.a2ui-button'),
68+
] as HTMLButtonElement[];
69+
const discardBtn = buttons.find(b => b.textContent.includes('Discard'))!;
70+
expect(discardBtn).withContext('Should find Discard button').toBeTruthy();
71+
72+
discardBtn.click();
73+
fixture.detectChanges();
74+
await wait(50);
75+
fixture.detectChanges();
76+
77+
expect(component.eventsLog.length).toBe(1);
78+
expect(component.eventsLog[0].action.name).toBe('discard');
79+
});
80+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {getCanvas, loadExample} from './test_utils';
18+
19+
describe('Example: Calendar Day', () => {
20+
let textContent: string;
21+
22+
beforeEach(async () => {
23+
await loadExample('Calendar Day');
24+
textContent = getCanvas().textContent;
25+
});
26+
27+
it('should render text content', async () => {
28+
expect(textContent).toContain('Lunch');
29+
expect(textContent).toContain('12:00 - 12:45 PM');
30+
expect(textContent).toContain('Q1 roadmap review');
31+
expect(textContent).toContain('1:00 - 2:00 PM');
32+
expect(textContent).toContain('Team standup');
33+
expect(textContent).toContain('3:30 - 4:00 PM');
34+
expect(textContent).toContain('Add to calendar');
35+
expect(textContent).toContain('Discard');
36+
});
37+
38+
it('should render date and day name', async () => {
39+
const hasDayNumber = textContent.includes('28') || textContent.includes('27');
40+
expect(hasDayNumber).withContext('Should render day number').toBeTruthy();
41+
42+
// Check for day name (approximate)
43+
const hasDayName =
44+
textContent.includes('Sunday') ||
45+
textContent.includes('Monday') ||
46+
textContent.includes('Tuesday') ||
47+
textContent.includes('Wednesday') ||
48+
textContent.includes('Thursday') ||
49+
textContent.includes('Friday') ||
50+
textContent.includes('Saturday');
51+
expect(hasDayName).withContext('Should render day name').toBeTruthy();
52+
});
53+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {getCanvas, loadExample} from './test_utils';
18+
19+
describe('Example: Weather Current', () => {
20+
let textContent: string;
21+
22+
beforeEach(async () => {
23+
await loadExample('Weather Current');
24+
textContent = getCanvas().textContent;
25+
});
26+
27+
it('should render main content', async () => {
28+
expect(textContent).toContain('Austin, TX');
29+
expect(textContent).toContain('Clear skies with light breeze');
30+
expect(textContent).toContain('72°');
31+
expect(textContent).toContain('58°');
32+
});
33+
34+
it('should render icons', async () => {
35+
expect(textContent).toContain('☀️');
36+
expect(textContent).toContain('⛅');
37+
});
38+
39+
it('should render forecast temperatures', async () => {
40+
expect(textContent).toContain('74°');
41+
expect(textContent).toContain('76°');
42+
expect(textContent).toContain('71°');
43+
expect(textContent).toContain('73°');
44+
expect(textContent).toContain('75°');
45+
});
46+
47+
it('should render day names for forecast', async () => {
48+
const hasDayName =
49+
textContent.includes('Tue') ||
50+
textContent.includes('Wed') ||
51+
textContent.includes('Thu') ||
52+
textContent.includes('Fri') ||
53+
textContent.includes('Sat');
54+
expect(hasDayName).withContext('Should render day names for forecast').toBeTruthy();
55+
});
56+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {ComponentFixture} from '@angular/core/testing';
18+
import {DemoComponent} from '../demo.component';
19+
import {getCanvas, loadExample, wait} from './test_utils';
20+
21+
describe('Example: Product Card', () => {
22+
let fixture: ComponentFixture<DemoComponent>;
23+
let component: DemoComponent;
24+
let textContent: string;
25+
26+
beforeEach(async () => {
27+
fixture = await loadExample('Product Card');
28+
component = fixture.componentInstance;
29+
textContent = getCanvas().textContent;
30+
});
31+
32+
it('should render text content', async () => {
33+
expect(textContent).toContain('Wireless Headphones Pro');
34+
expect(textContent).toContain('★★★★★');
35+
expect(textContent).toContain('2,847');
36+
expect(textContent).toContain('reviews');
37+
expect(textContent).toContain('Add to Cart');
38+
});
39+
40+
it('should render formatted currency', async () => {
41+
expect(textContent).toContain('199.99');
42+
expect(textContent).toContain('249.99');
43+
});
44+
45+
it('should render image', async () => {
46+
const img = fixture.nativeElement.querySelector('img');
47+
expect(img).toBeTruthy();
48+
expect(img.getAttribute('src')).toBe(
49+
'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=200&fit=crop',
50+
);
51+
});
52+
53+
it('should handle button click', async () => {
54+
const button = fixture.nativeElement.querySelector('.a2ui-button');
55+
expect(button).toBeTruthy();
56+
57+
button.click();
58+
fixture.detectChanges();
59+
60+
// Wait for event to be recorded in eventsLog
61+
await wait(50);
62+
fixture.detectChanges();
63+
64+
expect(component.eventsLog.length).toBe(1);
65+
expect(component.eventsLog[0].action.name).toBe('addToCart');
66+
});
67+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {ComponentFixture} from '@angular/core/testing';
18+
import {DemoComponent} from '../demo.component';
19+
import {getCanvas, loadExample} from './test_utils';
20+
21+
describe('Example: Music Player', () => {
22+
let fixture: ComponentFixture<DemoComponent>;
23+
let textContent: string;
24+
25+
beforeEach(async () => {
26+
fixture = await loadExample('Music Player');
27+
textContent = getCanvas().textContent;
28+
});
29+
30+
it('should render text content', async () => {
31+
expect(textContent).toContain('Blinding Lights');
32+
expect(textContent).toContain('The Weeknd');
33+
expect(textContent).toContain('1:48');
34+
expect(textContent).toContain('4:22');
35+
});
36+
37+
it('should render image', async () => {
38+
const img = fixture.nativeElement.querySelector('img');
39+
expect(img).toBeTruthy();
40+
expect(img.getAttribute('src')).toBe(
41+
'https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=300&h=300&fit=crop',
42+
);
43+
});
44+
45+
it('should render icons', async () => {
46+
expect(textContent).toContain('skip_previous');
47+
expect(textContent).toContain('skip_next');
48+
expect(textContent).toContain('pause');
49+
});
50+
});

0 commit comments

Comments
 (0)