feat: add typed WRN declarations
This commit is contained in:
@@ -66,6 +66,80 @@ test("parses a component with props and state", () => {
|
||||
expect(ast.states.map((s) => s.name)).toEqual(["count"]);
|
||||
});
|
||||
|
||||
test("typed props, required props, state, custom types, and function parameters compile", () => {
|
||||
const source = `component TypedPicker {
|
||||
types {
|
||||
interface DateOption { value: string; label: string }
|
||||
type ChangeHandler = (value: string) => void
|
||||
}
|
||||
props {
|
||||
value: string = ""
|
||||
options: DateOption[] = []
|
||||
required: boolean = false
|
||||
onChange: ChangeHandler
|
||||
}
|
||||
state open: boolean = false
|
||||
functions {
|
||||
function choose(option: DateOption, index: number): void {
|
||||
open = false
|
||||
onChange(option.value)
|
||||
}
|
||||
}
|
||||
view { <button @click="choose(options[0], 0)">{value}</button> }
|
||||
}`;
|
||||
const ast = parse(source);
|
||||
expect(ast.props.map(({ name, valueType, required }) => ({ name, valueType, required }))).toEqual(
|
||||
[
|
||||
{ name: "value", valueType: "string", required: false },
|
||||
{ name: "options", valueType: "DateOption[]", required: false },
|
||||
{ name: "required", valueType: "boolean", required: false },
|
||||
{ name: "onChange", valueType: "ChangeHandler", required: true },
|
||||
],
|
||||
);
|
||||
expect(ast.states[0]).toMatchObject({ name: "open", valueType: "boolean", expr: "false" });
|
||||
const output = generate(ast);
|
||||
expect(output).toContain("interface DateOption");
|
||||
expect(output).toContain("export interface TypedPickerProps");
|
||||
expect(output).toContain('"onChange": ChangeHandler;');
|
||||
expect(output).toContain("let open: boolean = (false)");
|
||||
expect(output).toContain("function choose(option: DateOption, index: number): void");
|
||||
const behavior = extractCompiledBehavior(output);
|
||||
expect(behavior.functions).toContain("function choose(option, index)");
|
||||
expect(behavior.functions).not.toContain("option: DateOption");
|
||||
});
|
||||
|
||||
test("typed declarations reject obvious initializer mismatches", () => {
|
||||
expect(() =>
|
||||
parse(`component Invalid {
|
||||
props {
|
||||
required: boolean = "yes"
|
||||
}
|
||||
view { <div></div> }
|
||||
}`),
|
||||
).toThrow("Prop 'required' is declared as boolean, but its initializer is string");
|
||||
expect(() => parse(`page Invalid { state count: number = false\n view { <p></p> } }`)).toThrow(
|
||||
"State 'count' is declared as number, but its initializer is boolean",
|
||||
);
|
||||
});
|
||||
|
||||
test("typed props enforce required values and runtime-compatible input", async () => {
|
||||
const component = await compileAndImport(`component TypedInput {
|
||||
props {
|
||||
label: string
|
||||
count: number = 0
|
||||
enabled: boolean = false
|
||||
}
|
||||
view { <span>{label}:{count}:{enabled}</span> }
|
||||
}`);
|
||||
const render = component.render as (props?: Record<string, unknown>) => string;
|
||||
expect(() => render()).toThrow("TypedInput requires prop 'label' (string)");
|
||||
expect(render({ label: "Total", count: "4", enabled: "true" })).toContain(
|
||||
"<span>Total:4:true</span>",
|
||||
);
|
||||
expect(() => render({ label: "Total", count: "many" })).toThrow("Expected a finite number prop");
|
||||
expect(() => render({ label: "Total", enabled: "sometimes" })).toThrow("Expected a boolean prop");
|
||||
});
|
||||
|
||||
test("HTML view: void elements, boolean attrs, comments, lone <", () => {
|
||||
const ast = parse(
|
||||
`component T {\n view {\n <input type="text" disabled>\n <br/>\n <!-- comment -->\n <p>a < b</p>\n }\n}`,
|
||||
|
||||
Reference in New Issue
Block a user