Skip to content

Commit 369e96c

Browse files
crisbetoAndrewKushnir
authored andcommitted
refactor(compiler-cli): split input and model tests (#54387)
Splits up the tests for `input()` and `model()` into separate files. PR Close #54387
1 parent 2363e77 commit 369e96c

2 files changed

Lines changed: 314 additions & 298 deletions

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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.io/license
7+
*/
8+
9+
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
10+
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
11+
12+
import {NgtscTestEnvironment} from './env';
13+
14+
const testFiles = loadStandardTestFiles();
15+
16+
runInEachFileSystem(() => {
17+
describe('initializer-based input() API', () => {
18+
let env!: NgtscTestEnvironment;
19+
20+
beforeEach(() => {
21+
env = NgtscTestEnvironment.setup(testFiles);
22+
env.tsconfig({strictTemplates: true});
23+
});
24+
25+
it('should handle a basic, primitive valued input', () => {
26+
env.write('test.ts', `
27+
import {Directive, input} from '@angular/core';
28+
29+
@Directive()
30+
export class TestDir {
31+
data = input('test');
32+
}
33+
`);
34+
env.driveMain();
35+
const js = env.getContents('test.js');
36+
expect(js).toContain('inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }');
37+
});
38+
39+
it('should fail if @Input is applied on signal input member', () => {
40+
env.write('test.ts', `
41+
import {Directive, Input, input} from '@angular/core';
42+
43+
@Directive()
44+
export class TestDir {
45+
@Input() data = input('test');
46+
}
47+
`);
48+
const diagnostics = env.driveDiagnostics();
49+
expect(diagnostics).toEqual([jasmine.objectContaining({
50+
messageText: `Using @Input with a signal input is not allowed.`,
51+
})]);
52+
});
53+
54+
it('should fail if signal input is also declared in `inputs` decorator field.', () => {
55+
env.write('test.ts', `
56+
import {Directive, input} from '@angular/core';
57+
58+
@Directive({
59+
inputs: ['data'],
60+
})
61+
export class TestDir {
62+
data = input('test');
63+
}
64+
`);
65+
const diagnostics = env.driveDiagnostics();
66+
expect(diagnostics).toEqual([
67+
jasmine.objectContaining({
68+
messageText: 'Input "data" is also declared as non-signal in @Directive.',
69+
}),
70+
]);
71+
});
72+
73+
it('should fail if signal input declares a non-statically analyzable alias', () => {
74+
env.write('test.ts', `
75+
import {Directive, input} from '@angular/core';
76+
77+
const ALIAS = 'bla';
78+
79+
@Directive({
80+
inputs: ['data'],
81+
})
82+
export class TestDir {
83+
data = input('test', {alias: ALIAS});
84+
}
85+
`);
86+
const diagnostics = env.driveDiagnostics();
87+
expect(diagnostics).toEqual([
88+
jasmine.objectContaining({
89+
messageText: 'Alias needs to be a string that is statically analyzable.',
90+
}),
91+
]);
92+
});
93+
94+
it('should fail if signal input declares a non-statically analyzable options', () => {
95+
env.write('test.ts', `
96+
import {Directive, input} from '@angular/core';
97+
98+
const OPTIONS = {};
99+
100+
@Directive({
101+
inputs: ['data'],
102+
})
103+
export class TestDir {
104+
data = input('test', OPTIONS);
105+
}
106+
`);
107+
const diagnostics = env.driveDiagnostics();
108+
expect(diagnostics).toEqual([
109+
jasmine.objectContaining({
110+
messageText: 'Argument needs to be an object literal that is statically analyzable.',
111+
}),
112+
]);
113+
});
114+
115+
it('should fail if signal input is declared on static member', () => {
116+
env.write('test.ts', `
117+
import {Directive, input} from '@angular/core';
118+
119+
@Directive()
120+
export class TestDir {
121+
static data = input('test');
122+
}
123+
`);
124+
const diagnostics = env.driveDiagnostics();
125+
expect(diagnostics).toEqual([
126+
jasmine.objectContaining({
127+
messageText: 'Input "data" is incorrectly declared as static member of "TestDir".',
128+
}),
129+
]);
130+
});
131+
132+
it('should handle an alias configured, primitive valued input', () => {
133+
env.write('test.ts', `
134+
import {Directive, input} from '@angular/core';
135+
136+
@Directive()
137+
export class TestDir {
138+
data = input('test', {
139+
alias: 'publicName',
140+
});
141+
}
142+
`);
143+
env.driveMain();
144+
const js = env.getContents('test.js');
145+
expect(js).toContain('inputs: { data: [i0.ɵɵInputFlags.SignalBased, "publicName", "data"] }');
146+
});
147+
148+
it('should error if a required input declares an initial value', () => {
149+
env.write('test.ts', `
150+
import {Directive, input} from '@angular/core';
151+
152+
@Directive()
153+
export class TestDir {
154+
data = input.required({
155+
initialValue: 'bla',
156+
});
157+
}
158+
`);
159+
const diagnostics = env.driveDiagnostics();
160+
expect(diagnostics[0].messageText).toEqual(jasmine.objectContaining({
161+
messageText: 'No overload matches this call.',
162+
}));
163+
});
164+
165+
166+
it('should handle a transform and required input', () => {
167+
env.write('test.ts', `
168+
import {Directive, input} from '@angular/core';
169+
170+
@Directive()
171+
export class TestDir {
172+
data = input.required({
173+
transform: (v: string|number) => 'works',
174+
});
175+
}
176+
`);
177+
env.driveMain();
178+
expect(env.getContents('test.js'))
179+
.toContain(`inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }`);
180+
expect(env.getContents('test.d.ts')).toContain('"required": true; "isSignal": true;');
181+
expect(env.getContents('test.d.ts'))
182+
.withContext(
183+
'Expected no coercion member as input signal captures the write type of the transform')
184+
.not.toContain('ngAcceptInputType');
185+
});
186+
187+
188+
it('should handle a non-primitive initial value', () => {
189+
env.write('test.ts', `
190+
import {Directive, input} from '@angular/core';
191+
192+
@Directive()
193+
export class TestDir {
194+
data = input(/default pattern/);
195+
}
196+
`);
197+
env.driveMain();
198+
expect(env.getContents('test.js'))
199+
.toContain(`inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }`);
200+
});
201+
202+
// TODO(crisbeto): we may not want to support this combination. Will discuss with the team.
203+
it('should report mixed two-way binding with a signal input', () => {
204+
env.write('test.ts', `
205+
import {Component, Directive, input, Output, EventEmitter} from '@angular/core';
206+
207+
@Directive({standalone: true, selector: '[dir]'})
208+
export class TestDir {
209+
value = input('hello');
210+
@Output() valueChange = new EventEmitter<string>();
211+
}
212+
213+
@Component({
214+
standalone: true,
215+
template: \`<div dir [(value)]="value"></div>\`,
216+
imports: [TestDir],
217+
})
218+
export class TestComp {
219+
value = 1;
220+
}
221+
`);
222+
223+
const diags = env.driveDiagnostics();
224+
expect(diags.length).toBe(1);
225+
expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`);
226+
});
227+
228+
describe('type checking', () => {
229+
it('should work', () => {
230+
env.write('test.ts', `
231+
import {Component, Directive, input} from '@angular/core';
232+
233+
@Directive({
234+
selector: '[directiveName]',
235+
standalone: true,
236+
})
237+
export class TestDir {
238+
data = input(1);
239+
}
240+
241+
@Component({
242+
standalone: true,
243+
template: \`<div directiveName [data]="false"></div>\`,
244+
imports: [TestDir],
245+
})
246+
export class TestComp {
247+
}
248+
`);
249+
250+
const diagnostics = env.driveDiagnostics();
251+
expect(diagnostics.length).toBe(1);
252+
expect(diagnostics[0].messageText)
253+
.toBe(`Type 'boolean' is not assignable to type 'number'.`);
254+
});
255+
256+
it('should work with transforms', () => {
257+
env.write('test.ts', `
258+
import {Component, Directive, input} from '@angular/core';
259+
260+
@Directive({
261+
selector: '[directiveName]',
262+
standalone: true,
263+
})
264+
export class TestDir {
265+
data = input.required({
266+
transform: (v: string|number) => 'works',
267+
});
268+
}
269+
270+
@Component({
271+
standalone: true,
272+
template: \`<div directiveName [data]="false"></div>\`,
273+
imports: [TestDir],
274+
})
275+
export class TestComp {
276+
}
277+
`);
278+
279+
const diagnostics = env.driveDiagnostics();
280+
expect(diagnostics.length).toBe(1);
281+
expect(diagnostics[0].messageText)
282+
.toBe(`Type 'boolean' is not assignable to type 'string | number'.`);
283+
});
284+
285+
it('should report unset required inputs', () => {
286+
env.write('test.ts', `
287+
import {Component, Directive, input} from '@angular/core';
288+
289+
@Directive({
290+
selector: '[directiveName]',
291+
standalone: true,
292+
})
293+
export class TestDir {
294+
data = input.required<boolean>();
295+
}
296+
297+
@Component({
298+
standalone: true,
299+
template: \`<div directiveName></div>\`,
300+
imports: [TestDir],
301+
})
302+
export class TestComp {
303+
}
304+
`);
305+
306+
const diagnostics = env.driveDiagnostics();
307+
expect(diagnostics.length).toBe(1);
308+
expect(diagnostics[0].messageText)
309+
.toBe(`Required input 'data' from directive TestDir must be specified.`);
310+
});
311+
});
312+
});
313+
});

0 commit comments

Comments
 (0)