Skip to content

Commit bfd10b1

Browse files
committed
testing inputs and outputs
1 parent 6bed4f8 commit bfd10b1

9 files changed

Lines changed: 303 additions & 23 deletions

05-testing/02-angular-2025/07-components-io.md

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ npx ng g c dashboard
99
```
1010

1111
```bash
12-
npx ng g c dashboard/dashboard-quote --flat
12+
npx ng g c dashboard/dashboard-quote --inline-template --flat
1313
```
1414

1515
Update `dashnoard-quote.component.ts`
@@ -38,6 +38,72 @@ export class DashboardQuoteComponent {
3838
}
3939
```
4040

41+
Update `dashnoard-quote.component.css`
42+
43+
```css
44+
.quote {
45+
padding: 20px;
46+
position: relative;
47+
text-align: center;
48+
color: #eee;
49+
max-height: 120px;
50+
width: 100%;
51+
min-width: 120px;
52+
background-color: #607d8b;
53+
border-radius: 2px;
54+
}
55+
56+
.quote:hover {
57+
background-color: #eee;
58+
cursor: pointer;
59+
color: #607d8b;
60+
}
61+
62+
@media (max-width: 600px) {
63+
.quote {
64+
font-size: 10px;
65+
max-height: 75px;
66+
}
67+
}
68+
69+
@media (max-width: 1024px) {
70+
.quote {
71+
min-width: 60px;
72+
}
73+
}
74+
```
75+
76+
Update `twain/twain.service.ts`
77+
78+
```diff
79+
@Injectable({
80+
providedIn: 'root',
81+
})
82+
export class TwainService {
83+
- private url = 'https://dummyjson.com/quotesrandom';
84+
+ private baseUrl = 'https://dummyjson.com/quotes';
85+
86+
constructor(private http: HttpClient) {}
87+
88+
getQuote(): Observable<string> {
89+
- return this.http.get<Quote>(this.url).pipe(
90+
+ return this.http.get<Quote>(`${this.baseUrl}/random`).pipe(
91+
map((q: Quote) => q.quote),
92+
catchError((err) => throwError(() => 'Can not get quote'))
93+
);
94+
}
95+
+
96+
+ getQuotes(): Observable<Quote[]> {
97+
+ return this.http.get<{ quotes: any[] }>(`${this.baseUrl}`).pipe(
98+
+ map(({ quotes }) => {
99+
+ return quotes.map((q) => ({ id: q.id, quote: q.quote }));
100+
+ }),
101+
+ catchError((err) => throwError(() => 'Can not get quotes'))
102+
+ );
103+
+ }
104+
}
105+
```
106+
41107
Update `dashboard.component.html`
42108

43109
```html
@@ -91,6 +157,47 @@ export class DashboardComponent implements OnInit {
91157
}
92158
```
93159

160+
Update `dashboard.component.css`
161+
162+
```css
163+
[class*="col-"] {
164+
float: left;
165+
}
166+
*,
167+
*::after,
168+
*::before {
169+
box-sizing: border-box;
170+
}
171+
h3 {
172+
text-align: center;
173+
margin-bottom: 0;
174+
}
175+
[class*="col-"] {
176+
padding-right: 20px;
177+
padding-bottom: 20px;
178+
}
179+
[class*="col-"]:last-of-type {
180+
padding-right: 0;
181+
}
182+
.grid {
183+
margin: 0;
184+
}
185+
.col-1-4 {
186+
width: 25%;
187+
}
188+
.grid-pad {
189+
padding: 10px 0;
190+
}
191+
.grid-pad > [class*="col-"]:last-of-type {
192+
padding-right: 20px;
193+
}
194+
@media (max-width: 1024px) {
195+
.grid {
196+
margin: 0;
197+
}
198+
}
199+
```
200+
94201
### Test `DashboardQuoteComponent` standalone
95202

96203
Update `dashboard-quote.component.spec.ts`
@@ -128,11 +235,25 @@ describe("DashboardQuoteComponent", () => {
128235
});
129236
```
130237

238+
```bash
239+
npx ng test --include app/dashboard/dashboard-quote.component.spec.ts
240+
```
241+
131242
### Clicking
132243

133244
Update `dashboard-quote.component.spec.ts`
134245

246+
```diff
247+
import { ComponentFixture, TestBed } from '@angular/core/testing';
248+
249+
import { DashboardQuoteComponent } from './dashboard-quote.component';
250+
import { Quote } from "../twain/quote";
251+
+import { By } from '@angular/platform-browser';
252+
....
253+
```
254+
135255
```ts
256+
// ....
136257
it("should raise selected event when clicked (triggerEventHandler)", async () => {
137258
const quoteDe = fixture.debugElement.query(By.css(".quote"));
138259
const expectedQuote: Quote = {
@@ -148,6 +269,7 @@ it("should raise selected event when clicked (triggerEventHandler)", async () =>
148269
quoteDe.triggerEventHandler("click");
149270
expect(selectedQuote).toBe(expectedQuote);
150271
});
272+
// ....
151273
```
152274

153275
The component's selected property returns an `EventEmitter`, which looks like an RxJS synchronous `Observable` to consumers. The test subscribes to it _explicitly_ just as the host component does _implicitly_.
@@ -159,6 +281,7 @@ The Angular `DebugElement.triggerEventHandler` can raise _any data-bound event_
159281
### Click the element
160282

161283
```ts
284+
// ....
162285
it("should raise selected event when clicked (element.click)", async () => {
163286
const quoteEl = fixture.nativeElement.querySelector(".quote");
164287
const expectedQuote: Quote = {
@@ -175,6 +298,7 @@ it("should raise selected event when clicked (element.click)", async () => {
175298
quoteEl.click();
176299
expect(selectedQuote).toBe(expectedQuote);
177300
});
301+
// ....
178302
```
179303

180304
> Call native's element own `click()`
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.quote {
2+
padding: 20px;
3+
position: relative;
4+
text-align: center;
5+
color: #eee;
6+
max-height: 120px;
7+
width: 100%;
8+
min-width: 120px;
9+
background-color: #607d8b;
10+
border-radius: 2px;
11+
}
12+
13+
.quote:hover {
14+
background-color: #eee;
15+
cursor: pointer;
16+
color: #607d8b;
17+
}
18+
19+
@media (max-width: 600px) {
20+
.quote {
21+
font-size: 10px;
22+
max-height: 75px;
23+
}
24+
}
25+
26+
@media (max-width: 1024px) {
27+
.quote {
28+
min-width: 60px;
29+
}
30+
}

05-testing/02-angular-2025/solution/application/src/app/dashboard/dashboard-quote.component.html

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,64 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22

33
import { DashboardQuoteComponent } from './dashboard-quote.component';
4+
import { Quote } from "../twain/quote";
5+
import { By } from '@angular/platform-browser';
46

57
describe('DashboardQuoteComponent', () => {
68
let component: DashboardQuoteComponent;
79
let fixture: ComponentFixture<DashboardQuoteComponent>;
810

9-
beforeEach(async () => {
10-
await TestBed.configureTestingModule({
11+
beforeEach(() => {
12+
TestBed.configureTestingModule({
1113
imports: [DashboardQuoteComponent]
12-
})
13-
.compileComponents();
14+
});
1415

1516
fixture = TestBed.createComponent(DashboardQuoteComponent);
1617
component = fixture.componentInstance;
18+
});
19+
20+
it("should display quote", async () => {
21+
const quoteEl = fixture.nativeElement.querySelector(".quote");
22+
const expectedQuote: Quote = {
23+
id: 1,
24+
quote: "All people live expecting die",
25+
};
26+
fixture.componentRef.setInput("quote", expectedQuote);
27+
fixture.detectChanges();
28+
await fixture.whenStable();
29+
expect(quoteEl.textContent).toContain(expectedQuote.quote);
30+
});
31+
32+
it("should raise selected event when clicked (triggerEventHandler)", async () => {
33+
const quoteDe = fixture.debugElement.query(By.css(".quote"));
34+
const expectedQuote: Quote = {
35+
id: 1,
36+
quote: "All people live expecting die",
37+
};
38+
fixture.componentRef.setInput("quote", expectedQuote);
1739
fixture.detectChanges();
40+
await fixture.whenStable();
41+
42+
let selectedQuote: Quote | undefined;
43+
component.selected.subscribe((quote: Quote) => (selectedQuote = quote));
44+
quoteDe.triggerEventHandler("click");
45+
expect(selectedQuote).toBe(expectedQuote);
1846
});
1947

20-
it('should create', () => {
21-
expect(component).toBeTruthy();
48+
it("should raise selected event when clicked (element.click)", async () => {
49+
const quoteEl = fixture.nativeElement.querySelector(".quote");
50+
const expectedQuote: Quote = {
51+
id: 1,
52+
quote: "All people live expecting die",
53+
};
54+
fixture.componentRef.setInput("quote", expectedQuote);
55+
fixture.detectChanges();
56+
await fixture.whenStable();
57+
58+
let selectedQuote: Quote | undefined;
59+
component.selected.subscribe((quote: Quote) => (selectedQuote = quote));
60+
61+
quoteEl.click();
62+
expect(selectedQuote).toBe(expectedQuote);
2263
});
2364
});
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
import { Component } from '@angular/core';
1+
import { Component, input, output } from "@angular/core";
2+
import { Quote } from "../twain/quote";
23

34
@Component({
4-
selector: 'app-dashboard-quote',
5+
selector: "app-dashboard-quote",
56
imports: [],
6-
templateUrl: './dashboard-quote.component.html',
7-
styleUrl: './dashboard-quote.component.css'
7+
template: `
8+
<button type="button" (click)="click()" class="quote">
9+
{{ quote().quote }}
10+
</button>
11+
`,
12+
styleUrl: "./dashboard-quote.component.css",
813
})
914
export class DashboardQuoteComponent {
15+
quote = input.required<Quote>();
16+
selected = output<Quote>();
1017

11-
}
18+
click() {
19+
this.selected.emit(this.quote());
20+
}
21+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[class*="col-"] {
2+
float: left;
3+
}
4+
*,
5+
*::after,
6+
*::before {
7+
box-sizing: border-box;
8+
}
9+
h3 {
10+
text-align: center;
11+
margin-bottom: 0;
12+
}
13+
[class*="col-"] {
14+
padding-right: 20px;
15+
padding-bottom: 20px;
16+
}
17+
[class*="col-"]:last-of-type {
18+
padding-right: 0;
19+
}
20+
.grid {
21+
margin: 0;
22+
}
23+
.col-1-4 {
24+
width: 25%;
25+
}
26+
.grid-pad {
27+
padding: 10px 0;
28+
}
29+
.grid-pad > [class*="col-"]:last-of-type {
30+
padding-right: 20px;
31+
}
32+
@media (max-width: 1024px) {
33+
.grid {
34+
margin: 0;
35+
}
36+
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1-
<p>dashboard works!</p>
1+
<h2>{{ title }}</h2>
2+
3+
<div class="grid grid-pad">
4+
@for (quote of quotes; track quote) {
5+
<app-dashboard-quote
6+
class="col-1-4"
7+
[quote]="quote"
8+
(selected)="gotoDetail($any($event))"
9+
></app-dashboard-quote>
10+
}
11+
</div>

0 commit comments

Comments
 (0)