blob: 8c5f1e414a67bb5559359c32b1b3d95acc25a3c2 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import rule from '../lib/no-empty-view-output.ts';
import {RuleTester, typeCheckingOptions} from './utils/RuleTester.ts';
new RuleTester(typeCheckingOptions).run('no-empty-view-output', rule, {
valid: [
{
name: 'allows view function type with undefined output and undefined argument in view call',
code: `type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;
class MyWidget extends UI.Widget.Widget {
#view: View;
performUpdate(): void {
this.#view(input, undefined, this.contentElement);
}
}`,
},
{
name: 'allows non-empty ViewOutput interface and variable argument in view call',
code: `interface ViewOutput { focus: () => void; }
type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
class MyWidget extends UI.Widget.Widget {
#view: View;
performUpdate(): void {
const output: ViewOutput = {focus: () => {}};
this.#view(input, output, this.contentElement);
}
}`,
},
{
name: 'allows passing this.#viewOutput field as second argument',
code: `interface ViewOutput { focusInput?: () => void; }
type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
class MyWidget extends UI.Widget.Widget {
#view: View;
#viewOutput: ViewOutput = {};
performUpdate(): void {
this.#view(input, this.#viewOutput, this.contentElement);
}
}`,
},
{
name: 'allows passing {} to a view function when a non-empty ViewOutput is defined',
code: `interface ViewOutput { focusInput?: () => void; }
type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
class MyWidget extends UI.Widget.Widget {
#view: View;
clear(): void {
this.#view(emptyInput, {}, this.contentElement);
}
}`,
},
{
name: 'allows passing {} to a typed view function whose output type is not named *ViewOutput',
code: `interface CustomOutput { focusInput?: () => void; }
type View = (input: ViewInput, output: CustomOutput, target: HTMLElement) => void;
class MyWidget extends UI.Widget.Widget {
#view: View;
clear(): void {
this.#view(emptyInput, {}, this.contentElement);
}
}`,
},
{
name: 'allows Record<string, unknown> as View output parameter type',
code: 'export type View = (input: ViewInput, output: Record<string, unknown>, target: HTMLElement) => void;',
},
{
name: 'ignores non-void function types that happen to have view-like parameter names',
code: 'export type Formatter = (input: string, output: object, target: HTMLElement) => string;',
},
],
invalid: [
{
name: 'disallows typing View output parameter as object',
code: 'export type View = (input: ViewInput, output: object, target: HTMLElement) => void;',
errors: [
{messageId: 'noEmptyViewOutputType'},
],
output: 'export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;',
},
{
name: 'disallows typing View output parameter as empty type literal {}',
code: 'export type View = (input: ViewInput, output: {}, target: HTMLElement) => void;',
errors: [
{messageId: 'noEmptyViewOutputType'},
],
output: 'export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;',
},
{
name: 'disallows typing View output parameter as Record<string, never>',
code: 'export type View = (input: ViewInput, output: Record<string, never>, target: HTMLElement) => void;',
errors: [
{messageId: 'noEmptyViewOutputType'},
],
output: 'export type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;',
},
{
name: 'disallows typing View output parameter with an empty ViewOutput interface in the same file',
code: `interface ViewOutput {}
export type View = (input: ViewInput, _output: ViewOutput, target: HTMLElement) => void;`,
errors: [
{messageId: 'noEmptyViewOutputType'},
],
output: `interface ViewOutput {}
export type View = (input: ViewInput, _output: undefined, target: HTMLElement) => void;`,
},
{
name: 'disallows passing {} as output argument to this.#view(...)',
code: 'this.#view({authoringHint: this.#authoringHint}, {}, this.contentElement);',
errors: [
{messageId: 'noEmptyViewOutputArgument'},
],
output: 'this.#view({authoringHint: this.#authoringHint}, undefined, this.contentElement);',
},
{
name: 'disallows passing {} as output argument to DEFAULT_VIEW(...)',
code: 'DEFAULT_VIEW(input, {}, target);',
errors: [
{messageId: 'noEmptyViewOutputArgument'},
],
output: 'DEFAULT_VIEW(input, undefined, target);',
},
{
name: 'disallows passing {} to a view taking undefined even when another non-empty ViewOutput exists in the file',
code: `interface FirstViewOutput { focusInput?: () => void; }
type SecondView = (input: ViewInput, output: undefined, target: HTMLElement) => void;
class SecondWidget extends UI.Widget.Widget {
#view: SecondView;
performUpdate(): void {
this.#view(input, {}, this.contentElement);
}
}`,
errors: [
{messageId: 'noEmptyViewOutputArgument'},
],
output: `interface FirstViewOutput { focusInput?: () => void; }
type SecondView = (input: ViewInput, output: undefined, target: HTMLElement) => void;
class SecondWidget extends UI.Widget.Widget {
#view: SecondView;
performUpdate(): void {
this.#view(input, undefined, this.contentElement);
}
}`,
},
],
});