Skip to content

Conversation

platosha
Copy link
Contributor

Fixes #3436

platosha added 28 commits August 4, 2025 16:56
@platosha platosha changed the title feat(lit-form): preliminary object models support feat(lit-form, react-form): object models support Oct 6, 2025
@platosha platosha changed the title feat(lit-form, react-form): object models support feat: object models support Oct 6, 2025
@platosha platosha changed the title feat: object models support feat: form binder object models support Oct 6, 2025
@platosha platosha marked this pull request as ready for review October 6, 2025 07:23
Copy link

codecov bot commented Oct 6, 2025

Codecov Report

❌ Patch coverage is 97.05882% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.69%. Comparing base (75a2378) to head (d500bf6).

Files with missing lines Patch % Lines
packages/ts/lit-form/src/stringConverters.ts 82.35% 9 Missing ⚠️
packages/ts/lit-form/src/BinderNode.ts 95.65% 4 Missing and 2 partials ⚠️
packages/ts/models/src/m.ts 98.41% 2 Missing ⚠️
packages/ts/lit-form/src/Validation.ts 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3816      +/-   ##
==========================================
+ Coverage   86.27%   86.69%   +0.41%     
==========================================
  Files         123      127       +4     
  Lines        8474     8764     +290     
  Branches     1301     1356      +55     
==========================================
+ Hits         7311     7598     +287     
- Misses       1140     1143       +3     
  Partials       23       23              
Flag Coverage Δ
unittests 86.69% <97.05%> (+0.41%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

sonarqubecloud bot commented Oct 7, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots
54.8% Duplication on New Code (required ≤ 3%)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

"@vaadin/hilla-lit-form": "file:../../../ts/lit-form",
"@vaadin/icon": "25.0.0-alpha21",
"@vaadin/icons": "25.0.0-alpha21",
"@vaadin/icon": "25.0.0-alpha20",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, this is wrong? 🤔

constructor(context: Element, Model: DetachedModelConstructor<M>, config?: BinderConfiguration<Value<M>>) {
constructor(
context: Element,
modelClass: DetachedModelConstructor<M & AbstractModel> | (M & Model),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it also be ProvisionalModel?

Comment on lines +128 to +151
function getModelValidators<M extends ProvisionalModel>(model: M): ReadonlyArray<Validator<Value<M>>> {
if (model instanceof AbstractModel) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return model[_validators];
}

const validators: Array<Validator<Value<M>>> =
model instanceof NumberModel ? [new Validators.IsNumber(model[$optional]) as Validator<Value<M>>] : [];

for (const constraint of model[$constraints]) {
const validator = getConstraintValidator<Value<M>>(constraint);
validators.push(validator);
}

return validators;
}

function createEmptyValue<M extends ProvisionalModel>(model: M) {
if (model instanceof AbstractModel) {
return model.constructor.createEmptyValue();
}

return model[$defaultValue];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, it'd need some docs to describe that there are some checks for transition period. Or, maybe, I'd put it in another file with all the proxies.

while (model[_parent] instanceof AbstractModel) {
name = `${String(model[_key])}${name ? `.${name}` : ''}`;
model = model[_parent];
while (model[$owner] instanceof AbstractModel || model[$owner] === Model || model[$owner] instanceof Model) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
while (model[$owner] instanceof AbstractModel || model[$owner] === Model || model[$owner] instanceof Model) {
while (model[$owner] instanceof AbstractModel || model[$owner] instanceof Model) {

You don't have to separately check if it is a Model. instanceof should do it for you.

this.initializeValue();

const key = this.model[_key];
const key = this.model[$key];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused seeing a $key here. Probably, it requires a small description in a doc comment

export const $from = Symbol('from');
export const $to = Symbol('to');

export type ModelConverter<FM extends Model = Model, TM extends Model = Model> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also added an explicit reference to that StackOverflow question.

/**
* The model of a `Record<string, V>` data, which is a special case
* of `ObjectModel<Record<string, V>>` that is used to represent an arbitrary
* object with string keys, such as a Java `Map<String, Object>`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not the MapModel then? 🤔

*
* @param model - The model to get the value from.
*/
export function getValue<T>(this: void, model: Model<T>): T {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just a simple value?

export function useForm<M extends AbstractModel>(
Model: DetachedModelConstructor<M>,
export function useForm<M extends ProvisionalModel>(
modelClass: DetachedModelConstructor<M & AbstractModel> | (M & Model),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProvisionModel?

describe('@vaadin/hilla-react-form', () => {
type UseFormSpy = sinon.SinonSpy<Parameters<typeof _useForm>, ReturnType<typeof _useForm>>;
const useForm = sinon.spy(_useForm) as typeof _useForm;
const useForm = sinon.spy(_useForm) as unknown as typeof _useForm;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to remove cast to unknown? It looks kinda broken

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Object Models] Binder support
2 participants