Skip to content

Commit b5b1645

Browse files
committed
refactor(docs-infra): use resource() to highlight code in the home page code block
The home page code block created a new Promise in a `computed` whenever the code, language or theme changed, and unwrapped it with the `async` pipe. Load the highlighted code with `resource()` instead. Its params track the code, language and theme, so the code is highlighted again when any of them changes, and the template reads the value directly. This removes the last `AsyncPipe` usage in adev. Unlike the `async` pipe, `resource()` tracks its loading as pending work, so the application is only considered stable once the code has been highlighted. Add a spec covering the initial highlight, theme changes and the language input.
1 parent fc9b2d6 commit b5b1645

2 files changed

Lines changed: 92 additions & 12 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*!
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {signal} from '@angular/core';
10+
import {ComponentFixture, TestBed} from '@angular/core/testing';
11+
12+
import {ThemeManager} from '../../../../core/services/theme-manager.service';
13+
import {CodeHighlighter} from '../../code-highlighting/code-highlighter';
14+
import {CodeBlock} from './code-block';
15+
16+
interface HighlightOptions {
17+
cssVariablePrefix: string;
18+
lang: string;
19+
theme: string;
20+
}
21+
22+
describe('CodeBlock', () => {
23+
let fixture: ComponentFixture<CodeBlock>;
24+
let codeToHtml: jasmine.Spy<(code: string, options: HighlightOptions) => Promise<string>>;
25+
const resolvedTheme = signal<'dark' | 'light'>('light');
26+
27+
function renderedCode(): string {
28+
return fixture.nativeElement.querySelector('code').innerHTML;
29+
}
30+
31+
beforeEach(async () => {
32+
resolvedTheme.set('light');
33+
codeToHtml = jasmine
34+
.createSpy('codeToHtml')
35+
.and.callFake(
36+
async (code: string, options: HighlightOptions) =>
37+
`<span class="${options.theme}">${code}</span>`,
38+
);
39+
40+
TestBed.configureTestingModule({
41+
providers: [
42+
{provide: CodeHighlighter, useValue: {codeToHtml}},
43+
{provide: ThemeManager, useValue: {resolvedTheme}},
44+
],
45+
});
46+
47+
fixture = TestBed.createComponent(CodeBlock);
48+
fixture.componentRef.setInput('code', 'const answer = 42;');
49+
await fixture.whenStable();
50+
});
51+
52+
it('should render the highlighted code', () => {
53+
expect(codeToHtml).toHaveBeenCalledOnceWith('const answer = 42;', {
54+
cssVariablePrefix: '--shiki-',
55+
lang: 'angular-ts',
56+
theme: 'github-light',
57+
});
58+
expect(renderedCode()).toBe('<span class="github-light">const answer = 42;</span>');
59+
});
60+
61+
it('should highlight the code again when the theme changes', async () => {
62+
resolvedTheme.set('dark');
63+
await fixture.whenStable();
64+
65+
expect(renderedCode()).toBe('<span class="github-dark">const answer = 42;</span>');
66+
});
67+
68+
it('should highlight the code with the given language', async () => {
69+
fixture.componentRef.setInput('language', 'angular-html');
70+
await fixture.whenStable();
71+
72+
expect(codeToHtml).toHaveBeenCalledWith('const answer = 42;', {
73+
cssVariablePrefix: '--shiki-',
74+
lang: 'angular-html',
75+
theme: 'github-light',
76+
});
77+
});
78+
});

‎adev/src/app/features/home/components/code-block/code-block.ts‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {AsyncPipe} from '@angular/common';
10-
import {Component, computed, inject, input} from '@angular/core';
9+
import {Component, inject, input, resource} from '@angular/core';
1110
import {DomSanitizer} from '@angular/platform-browser';
1211
import {ThemeManager} from '../../../../core/services/theme-manager.service';
1312
import {CodeHighlighter} from '../../code-highlighting/code-highlighter';
1413

1514
@Component({
1615
selector: 'adev-code-block',
17-
template: `<pre><code [innerHTML]="highlightedCode() | async"></code></pre>`,
18-
imports: [AsyncPipe],
16+
template: `<pre><code [innerHTML]="highlightedCode.value()"></code></pre>`,
1917
styles: `
2018
::ng-deep pre {
2119
margin: 0;
@@ -29,15 +27,19 @@ export class CodeBlock {
2927
sanitizer = inject(DomSanitizer);
3028
theme = inject(ThemeManager);
3129

32-
highlightedCode = computed(() => {
33-
return this.codeHighlighter
34-
.codeToHtml(this.code(), {
30+
highlightedCode = resource({
31+
params: () => ({
32+
code: this.code(),
33+
lang: this.language(),
34+
theme: this.theme.resolvedTheme() === 'dark' ? 'github-dark' : 'github-light',
35+
}),
36+
loader: async ({params: {code, lang, theme}}) => {
37+
const highlightedHtml = await this.codeHighlighter.codeToHtml(code, {
3538
cssVariablePrefix: '--shiki-',
36-
lang: this.language(),
37-
theme: this.theme.resolvedTheme() === 'dark' ? 'github-dark' : 'github-light',
38-
})
39-
.then((hightlightedHtml) => {
40-
return this.sanitizer.bypassSecurityTrustHtml(hightlightedHtml);
39+
lang,
40+
theme,
4141
});
42+
return this.sanitizer.bypassSecurityTrustHtml(highlightedHtml);
43+
},
4244
});
4345
}

0 commit comments

Comments
 (0)