Skip to content

Commit

Permalink
refactor(core): set up runtime unit tests for let declarations
Browse files Browse the repository at this point in the history
Adds a temporary function that allows us to write unit tests for let declarations before they're released. Also sets up the file that we can use to add the tests over time.
  • Loading branch information
crisbeto committed May 9, 2024
1 parent b8a0b49 commit 81ac729
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 1 deletion.
13 changes: 12 additions & 1 deletion packages/compiler/src/jit_compiler_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ import {ResourceLoader} from './resource_loader';
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
import {SelectorMatcher} from './selector';

let enableLetSyntax = false;

/** Temporary utility that enables `@let` declarations in JIT compilations. */
export function ɵsetEnableLetSyntax(value: boolean): void {
enableLetSyntax = value;
}

export class CompilerFacadeImpl implements CompilerFacade {
FactoryTarget = FactoryTarget;
ResourceLoader = ResourceLoader;
Expand Down Expand Up @@ -716,7 +723,11 @@ function parseJitTemplate(
? InterpolationConfig.fromArray(interpolation)
: DEFAULT_INTERPOLATION_CONFIG;
// Parse the template and check for errors.
const parsed = parseTemplate(template, sourceMapUrl, {preserveWhitespaces, interpolationConfig});
const parsed = parseTemplate(template, sourceMapUrl, {
preserveWhitespaces,
interpolationConfig,
enableLetSyntax,
});
if (parsed.errors !== null) {
const errors = parsed.errors.map((err) => err.toString()).join(', ');
throw new Error(`Errors during JIT compilation of template for ${typeName}: ${errors}`);
Expand Down
119 changes: 119 additions & 0 deletions packages/core/test/acceptance/let_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Component, Directive, Output, EventEmitter, ErrorHandler} from '@angular/core';
import {ɵsetEnableLetSyntax} from '@angular/compiler/src/jit_compiler_facade';
import {TestBed} from '@angular/core/testing';

describe('@let declarations', () => {
beforeEach(() => ɵsetEnableLetSyntax(true));
afterEach(() => ɵsetEnableLetSyntax(false));

it('should update the value of a @let declaration over time', () => {
@Component({
standalone: true,
template: `
@let multiplier = 2;
@let result = value * multiplier;
{{value}} times {{multiplier}} is {{result}}
`,
})
class TestComponent {
value = 0;
}

const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('0 times 2 is 0');

fixture.componentInstance.value = 1;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('1 times 2 is 2');

fixture.componentInstance.value = 2;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('2 times 2 is 4');
});

it('should be able to access @let declarations from parent view before they are declared', () => {
@Component({
standalone: true,
template: `
@if (true) {
{{value}} times {{multiplier}} is {{result}}
}
@let multiplier = 2;
@let result = value * multiplier;
`,
})
class TestComponent {
value = 0;
}

const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('0 times 2 is 0');

fixture.componentInstance.value = 1;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('1 times 2 is 2');

fixture.componentInstance.value = 2;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('2 times 2 is 4');
});

it('should throw if a @let declaration is accessed before it is initialized', () => {
const errors: string[] = [];

@Directive({
selector: '[dir]',
standalone: true,
})
class TestDirective {
@Output() testEvent = new EventEmitter<void>();

ngOnInit() {
this.testEvent.emit();
}
}

@Component({
standalone: true,
imports: [TestDirective],
template: `
<div dir (testEvent)="callback(value)"></div>
@let value = 1;
`,
})
class TestComponent {
callback(_value: number) {}
}

TestBed.configureTestingModule({
imports: [TestComponent],
providers: [
// We can't use `toThrow` in the tests, because errors in listeners
// are caught. Capture them using a custom ErrorHandler instead.
{
provide: ErrorHandler,
useValue: {
handleError: (error: Error) => errors.push(error.message),
},
},
],
});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(errors.length).toBe(1);
expect(errors[0]).toContain(
'Attempting to access a @let declaration whose value is not available yet',
);
});
});

0 comments on commit 81ac729

Please sign in to comment.