Skip to content

Commit

Permalink
Added field name and made initial value modifiable
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrei15193 committed Jun 19, 2021
1 parent 2c8f852 commit 6254ee1
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 34 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-model-view-viewmodel",
"version": "1.0.0-beta5",
"version": "1.0.0-beta6",
"description": "A library for developing React applications using Model-View-ViewModel inspired by .NET",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
Expand Down
5 changes: 3 additions & 2 deletions src/form-field-collection-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ export abstract class FormFieldCollectionViewModel extends ViewModel implements
}

/** Registers a new FormFieldViewModel having the provided initial value and returns it.
* @param name - The name of the field.
* @param initialValue - The initial value of the field.
*/
protected addField<TValue>(initialValue: TValue): FormFieldViewModel<TValue> {
return this.registerField(new FormFieldViewModel<TValue>(initialValue));
protected addField<TValue>(name: string, initialValue: TValue): FormFieldViewModel<TValue> {
return this.registerField(new FormFieldViewModel<TValue>(name, initialValue));
}

/** Registers the provided FormFieldViewModel and returns it.
Expand Down
44 changes: 37 additions & 7 deletions src/form-field-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import { ViewModel } from './view-model';
* @template TValue - The type of values the field contains.
*/
export interface IFormFieldViewModel<TValue> extends INotifyPropertiesChanged, IReadOnlyValidatable {
/** The current value of the field. */
value: TValue;
/** The name of the field. */
readonly name: string;

/** The initial value of the field. Useful in scenarios where the input should be highlighted if the field has changed. */
readonly initialValue: TValue;

/** The current value of the field. */
value: TValue;

/** A flag indicating whether the field has been touched. Useful for cases when the error message should be displayed only if the field has been touched. */
isTouched: boolean;

Expand All @@ -23,23 +26,53 @@ export interface IFormFieldViewModel<TValue> extends INotifyPropertiesChanged, I
* @template TValue - The type of values the field contains.
*/
export class FormFieldViewModel<TValue> extends ViewModel implements IFormFieldViewModel<TValue>, IValidatable {
private _name: string;
private _value: TValue;
private _initialValue: TValue;
private _isTouched: boolean;
private _isFocused: boolean;
private _error: string | undefined;

/** Initializes a new instance of the FormFieldViewModel class.
* @param name - The name of the field.
* @param initalValue - The initial value of the field.
*/
public constructor(initalValue: TValue) {
public constructor(name: string, initalValue: TValue) {
super();
this.initialValue = initalValue;
this._name = name;
this._value = initalValue;
this._initialValue = initalValue;
this._isTouched = false;
this._isFocused = false;
this._error = undefined;
}

/** The name of the field. */
public get name(): string {
return this._name;
}

/** The name of the field. */
public set name(value: string) {
if (this._name !== value) {
this._name = value;
this.notifyPropertiesChanged('name');
}
}

/** The initial value of the field. Useful in scenarios where the input should be highlighted if the field has changed. */
public get initialValue(): TValue {
return this._initialValue;
}

/** The initial value of the field. Useful in scenarios where the input should be highlighted if the field has changed. */
public set initialValue(value: TValue) {
if (this._initialValue !== value) {
this._initialValue = value;
this.notifyPropertiesChanged('initialValue');
}
}

/** The current value of the field. */
public get value(): TValue {
return this._value;
Expand All @@ -53,9 +86,6 @@ export class FormFieldViewModel<TValue> extends ViewModel implements IFormFieldV
}
}

/** The initial value of the field. Useful in scenarios where the input should be highlighted if the field has changed. */
public readonly initialValue: TValue;

/** A flag indicating whether the field has been touched. Useful for cases when the error message should be displayed only if the field has been touched. */
public get isTouched(): boolean {
return this._isTouched;
Expand Down
28 changes: 14 additions & 14 deletions tests/form-field-collection-view-model-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { FormFieldCollectionViewModel } from '../src/form-field-collection-view-

describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): void => {
class MockFormFieldCollectionViewModel extends FormFieldCollectionViewModel {
public addField<TValue>(initialValue: TValue): FormFieldViewModel<TValue> {
return super.addField(initialValue);
public addField<TValue>(name: string, initialValue: TValue): FormFieldViewModel<TValue> {
return super.addField(name, initialValue);
}

public registerField<TValue>(field: FormFieldViewModel<TValue>): FormFieldViewModel<TValue> {
Expand Down Expand Up @@ -85,14 +85,14 @@ describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): vo
}
});

formFieldCollection.addField('');
formFieldCollection.addField('', '');

expect(invocationCount).is.equal(1);
});

it('registering and invalidating a field updates related flags', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();
const field = formFieldCollection.addField('');
const field = formFieldCollection.addField('', '');
let invocationCount = 0;
formFieldCollection.propertiesChanged.subscribe({
handle(subject, changedProperties) {
Expand All @@ -112,7 +112,7 @@ describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): vo

it('registering and validating a field updates related flags', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();
const field = formFieldCollection.addField('');
const field = formFieldCollection.addField('', '');
field.error = '';
let invocationCount = 0;
formFieldCollection.propertiesChanged.subscribe({
Expand All @@ -133,7 +133,7 @@ describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): vo

it('unregistering a field updates related flags', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();
const field = formFieldCollection.addField('');
const field = formFieldCollection.addField('', '');
let invocationCount = 0;
formFieldCollection.propertiesChanged.subscribe({
handle(subject, changedProperties) {
Expand All @@ -153,7 +153,7 @@ describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): vo

it('unregistering an invalid field updates related flags', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();
const field = formFieldCollection.addField('');
const field = formFieldCollection.addField('', '');
field.error = '';
let invocationCount = 0;
formFieldCollection.propertiesChanged.subscribe({
Expand All @@ -175,18 +175,18 @@ describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): vo
it('registering a field makes it available through the fields property', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();

const field1 = formFieldCollection.addField('');
const field2 = formFieldCollection.addField('');
const field3 = formFieldCollection.addField('');
const field1 = formFieldCollection.addField('', '');
const field2 = formFieldCollection.addField('', '');
const field3 = formFieldCollection.addField('', '');

expect(formFieldCollection.fields).is.deep.equal([field1, field2, field3]);
});

it('unregistering a field no longer makes it available through the fields property', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();
const field1 = formFieldCollection.addField('');
const field2 = formFieldCollection.addField('');
const field3 = formFieldCollection.addField('');
const field1 = formFieldCollection.addField('', '');
const field2 = formFieldCollection.addField('', '');
const field3 = formFieldCollection.addField('', '');

formFieldCollection.unregisterField(field2);

Expand All @@ -195,7 +195,7 @@ describe('form-field-collection-view-model/FormFieldCollectionViewModel', (): vo

it('registering a field twice and unregistering it once makes it available through the fields property once', (): void => {
const formFieldCollection = new MockFormFieldCollectionViewModel();
const field = formFieldCollection.addField('');
const field = formFieldCollection.addField('', '');
formFieldCollection.registerField(field);

formFieldCollection.unregisterField(field);
Expand Down
81 changes: 71 additions & 10 deletions tests/form-field-view-model-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { FormFieldViewModel } from '../src/form-field-view-model';
describe('form-field-view-model/FormFieldViewModel', (): void => {
it('creating form field initializes value with initial value and other fields with default values', (): void => {
const initilValue = {};
const formField = new FormFieldViewModel(initilValue);
const formField = new FormFieldViewModel('name', initilValue);

expect(formField.name).is.equal('name');
expect(formField.initialValue).is.equal(initilValue);
expect(formField.value).is.equal(initilValue);
expect(formField.isTouched).is.equal(false);
Expand All @@ -15,8 +16,68 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
expect(formField.error).is.equal(undefined);
});

it('setting name notifies subscribers', (): void => {
const formField = new FormFieldViewModel('name', 'initial-value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle(subject, changedProperties) {
invocationCount++;
expect(subject).is.equal(formField);
expect(changedProperties).is.deep.equal(['name']);
}
});

formField.name = 'new-name';

expect(invocationCount).is.equal(1);
});

it('setting same name does not notify subscribers', (): void => {
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle() {
invocationCount++;
}
});

formField.name = 'name';

expect(invocationCount).is.equal(0);
});

it('setting initial value notifies subscribers', (): void => {
const formField = new FormFieldViewModel('name', 'initial-value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle(subject, changedProperties) {
invocationCount++;
expect(subject).is.equal(formField);
expect(changedProperties).is.deep.equal(['initialValue']);
}
});

formField.initialValue = 'new-value';

expect(invocationCount).is.equal(1);
});

it('setting same initial value does not notify subscribers', (): void => {
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle() {
invocationCount++;
}
});

formField.initialValue = 'value';

expect(invocationCount).is.equal(0);
});

it('setting value notifies subscribers', (): void => {
const formField = new FormFieldViewModel('initial-value');
const formField = new FormFieldViewModel('name', 'initial-value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle(subject, changedProperties) {
Expand All @@ -32,7 +93,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting same value does not notify subscribers', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle() {
Expand All @@ -46,7 +107,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting isTouched notifies subscribers', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle(subject, changedProperties) {
Expand All @@ -62,7 +123,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting same isTouched does not notify subscribers', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle() {
Expand All @@ -76,7 +137,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting isFocused notifies subscribers', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle(subject, changedProperties) {
Expand All @@ -92,7 +153,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting same isFocused does not notify subscribers', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle() {
Expand All @@ -106,7 +167,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting error notifies subscribers and updates related flags', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
let invocationCount = 0;
formField.propertiesChanged.subscribe({
handle(subject, changedProperties) {
Expand All @@ -125,7 +186,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting same error does not notify subscribers', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
formField.error = '';
let invocationCount = 0;
formField.propertiesChanged.subscribe({
Expand All @@ -140,7 +201,7 @@ describe('form-field-view-model/FormFieldViewModel', (): void => {
});

it('setting error back to undefined notifies subscribers and updates related flags', (): void => {
const formField = new FormFieldViewModel('value');
const formField = new FormFieldViewModel('name', 'value');
formField.error = '';
let invocationCount = 0;
formField.propertiesChanged.subscribe({
Expand Down

0 comments on commit 6254ee1

Please sign in to comment.