Skip to content

Commit ebff871

Browse files
author
Martynas Žilinskas
authored
Feature/validation (#14)
* simplr-forms-core form-store emit action after value changed. * Added action-emitter as a dependency. Updated form-store-subscriber test. * Added subscriber and exposed all subscribers and subscriber in index.ts. * Fixed name: FormStoreHandlerSubscriber to FormStoresHandlerSubscriber. * Changed const enum -> enum. Added fieldProps FormStoreSubscriber tests with validation flag. * Fixed tests. Added validateField function. * Added action field registration. * Added OnRegistered listener for validation. * Update add listeners test. * Renamed from SubscriberClass -> Subscriber. * FormStoreSubscriber fieldOnRegistered added to RemoveFormListeners. And added missing check validationType. * Deleted compiled files. Added to .gitignore. * Updated validators base with ValidationError. * Fixed tests with errorMessage -> error change.
1 parent ef92be1 commit ebff871

27 files changed

+229
-158
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,6 @@ paket-files/
251251
.idea/
252252
*.sln.iml
253253
yarn.lock
254+
255+
dist
256+
@types

packages/simplr-forms-core/src/actions/form-store.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
import { FieldValue } from "../contracts/field";
2+
13
export class StateUpdated { }
24

5+
export class FieldRegistered {
6+
constructor(private fieldId: string, private initialValue: FieldValue) { }
7+
8+
public get FieldId() {
9+
return this.fieldId;
10+
}
11+
12+
public get InitialValue() {
13+
return this.initialValue;
14+
}
15+
}
16+
317
export class ValueChanged {
4-
constructor(private fieldId: string, private newValue: any) { }
18+
constructor(private fieldId: string, private newValue: FieldValue) { }
519

620
public get FieldId() {
721
return this.fieldId;

packages/simplr-forms-core/src/contracts/field.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface FieldStateRecord extends TypedRecord<FieldStateRecord>, FieldSt
3838
export interface FieldStatePropsRecord extends TypedRecord<FieldStatePropsRecord>, FieldStateProps { }
3939
export interface FormErrorRecord extends TypedRecord<FormErrorRecord>, FormError { }
4040

41-
export const enum FieldValidationType {
41+
export enum FieldValidationType {
4242
None,
4343
OnFieldRegistered = 1 << 1,
4444
OnValueChange = 1 << 2,

packages/simplr-forms-core/src/stores/form-store.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export class FormStore extends ActionEmitter {
8989
this.State = this.State.withMutations(state => {
9090
state.Fields = state.Fields.set(fieldId, recordify<FieldState, FieldStateRecord>(fieldState));
9191
});
92+
93+
this.emit(new Actions.FieldRegistered(fieldId, initialValue));
9294
}
9395

9496
public UnregisterField(fieldId: string) {
@@ -125,14 +127,14 @@ export class FormStore extends ActionEmitter {
125127
}
126128

127129
public ValueChanged(fieldId: string, newValue: FieldValue) {
128-
this.emit(new Actions.ValueChanged(fieldId, newValue));
129-
130130
this.State = this.State.withMutations(state => {
131131
const fieldState = state.Fields.get(fieldId);
132132
state.Fields = state.Fields.set(fieldId, fieldState.merge({
133133
Value: newValue
134134
} as FieldState));
135135
});
136+
137+
this.emit(new Actions.ValueChanged(fieldId, newValue));
136138
}
137139

138140
public async Validate(fieldId: string, validationPromise: Promise<void>) {
Lines changed: 81 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import * as React from "react";
2-
import { Stores, Actions } from "simplr-forms-core";
2+
import { Stores, Actions, Contracts as FormsCoreContracts } from "simplr-forms-core";
33
import * as Sinon from "sinon";
44

55
import { ContainsValidator } from "../../src/validators/index";
66
import { FormStoreSubscriber } from "../../src/subscribers/form-store-subscriber";
77

8+
const { FieldValidationType } = FormsCoreContracts;
9+
810
class MySubscriber extends FormStoreSubscriber {
9-
protected onValueChanged(action: Actions.ValueChanged) {
10-
return new Promise<any>((resolve, reject) => {
11-
super.onValueChanged(action).then(resolve).catch(reject);
12-
});
11+
public ValidateField(
12+
fieldId: string,
13+
value: FormsCoreContracts.FieldValue,
14+
validationType: FormsCoreContracts.FieldValidationType
15+
) {
16+
return super.ValidateField(fieldId, value, validationType);
1317
}
1418
}
1519

@@ -19,7 +23,7 @@ describe("FormStoreSubscriber", () => {
1923
sandbox = Sinon.sandbox.create();
2024
});
2125

22-
afterEach(function () {
26+
afterEach(() => {
2327
sandbox.restore();
2428
});
2529

@@ -29,12 +33,14 @@ describe("FormStoreSubscriber", () => {
2933

3034
expect(callback.called).toEqual(false);
3135

36+
expect(formStore.listeners(Actions.FieldRegistered).length).toBe(0);
3237
expect(formStore.listeners(Actions.PropsChanged).length).toBe(0);
3338
expect(formStore.listeners(Actions.ValueChanged).length).toBe(0);
3439

3540
new FormStoreSubscriber(formStore);
3641
expect(callback.called).toEqual(true);
3742

43+
expect(formStore.listeners(Actions.FieldRegistered).length).toBe(1);
3844
expect(formStore.listeners(Actions.PropsChanged).length).toBe(1);
3945
expect(formStore.listeners(Actions.ValueChanged).length).toBe(1);
4046
});
@@ -51,82 +57,107 @@ describe("FormStoreSubscriber", () => {
5157

5258
});
5359

54-
it("MUST validate when value changed without an error", async (done) => {
60+
it("MUST validate when specific validationType flag is present without error", async (done) => {
5561
const fieldId = "field-id";
56-
const nextValue = "valid value";
62+
const initialValue = "initial valid value";
5763
const errorMessage = "error message";
5864

5965
const validatorValidateCallback = sandbox.spy(ContainsValidator.prototype, "Validate");
60-
const fieldChildren = [<ContainsValidator value="valid" errorMessage={errorMessage} />];
66+
const fieldChildren = [<ContainsValidator value="valid" error={errorMessage} />];
6167
const formStore = new Stores.FormStore("form-id");
6268
const formStoreValidateCallback = sandbox.spy(Stores.FormStore.prototype, "Validate");
63-
const onValueChangedCallback = sandbox.spy(MySubscriber.prototype, "onValueChanged");
64-
new MySubscriber(formStore);
65-
66-
formStore.RegisterField(fieldId, "initial", { name: "field-name", children: fieldChildren });
67-
formStore.ValueChanged(fieldId, nextValue);
68-
69-
const [onValueChangedPromise] = onValueChangedCallback.returnValues;
70-
71-
try {
72-
expect(onValueChangedPromise).toBeDefined();
73-
expect(onValueChangedPromise.then).toBeDefined();
74-
} catch (err) {
75-
done.fail(err);
76-
}
69+
const subscriber = new MySubscriber(formStore);
7770

78-
await onValueChangedPromise;
71+
const fieldProps: FormsCoreContracts.FieldStateProps = {
72+
name: "field-name",
73+
children: fieldChildren,
74+
validationType: FieldValidationType.OnValueChange
75+
};
7976

77+
formStore.RegisterField(fieldId, initialValue, fieldProps);
8078
try {
79+
await subscriber.ValidateField(fieldId, initialValue, FieldValidationType.OnValueChange);
8180
expect(formStoreValidateCallback.called).toBe(true);
8281
expect(validatorValidateCallback.called).toBe(true);
8382
expect(formStore.GetField(fieldId).Error).toBeUndefined();
83+
84+
done();
8485
} catch (error) {
8586
done.fail(error);
8687
}
87-
done();
8888
});
8989

90-
it("MUST validate when value changed with an error", async (done) => {
90+
it("MUST validate when specific validationType flag is present with error", async (done) => {
9191
const fieldId = "field-id";
92-
const nextValue = "next value";
92+
const initialValue = "initial value";
9393
const errorMessage = "error message";
9494

9595
const validatorValidateCallback = sandbox.spy(ContainsValidator.prototype, "Validate");
96-
const fieldChildren = [<ContainsValidator value="ok" errorMessage={errorMessage} />];
96+
const fieldChildren = [<ContainsValidator value="valid" error={errorMessage} />];
9797
const formStore = new Stores.FormStore("form-id");
9898
const formStoreValidateCallback = sandbox.spy(Stores.FormStore.prototype, "Validate");
99-
const onValueChangedCallback = sandbox.spy(MySubscriber.prototype, "onValueChanged");
100-
new MySubscriber(formStore);
101-
102-
formStore.RegisterField(fieldId, "initial", { name: "field-name", children: fieldChildren });
103-
formStore.ValueChanged(fieldId, nextValue);
99+
const subscriber = new MySubscriber(formStore);
104100

105-
const [onValueChangedPromise] = onValueChangedCallback.returnValues;
101+
const fieldProps: FormsCoreContracts.FieldStateProps = {
102+
name: "field-name",
103+
children: fieldChildren,
104+
validationType: FieldValidationType.OnValueChange
105+
};
106106

107+
formStore.RegisterField(fieldId, initialValue, fieldProps);
107108
try {
108-
expect(onValueChangedPromise).toBeDefined();
109-
expect(onValueChangedPromise.then).toBeDefined();
110-
expect(formStore.GetField(fieldId).Validating).toBe(true);
111-
} catch (err) {
112-
done.fail(err);
113-
}
114-
115-
await onValueChangedPromise;
116-
117-
try {
118-
expect(formStore.GetField(fieldId).Validating).toBe(false);
109+
await subscriber.ValidateField(fieldId, initialValue, FieldValidationType.OnValueChange);
119110
expect(formStoreValidateCallback.called).toBe(true);
120111
expect(validatorValidateCallback.called).toBe(true);
121112
expect(formStore.GetField(fieldId).Error).toBeDefined();
122-
expect(formStore.GetField(fieldId).Error!.Message).toEqual(errorMessage);
113+
114+
done();
123115
} catch (error) {
124116
done.fail(error);
125117
}
126-
done();
127118
});
128119

129-
// TODO: OnPropsChanged tests
130-
it("validate when props changed without an error", () => { });
131-
it("validate when props changed with an error", () => { });
120+
it("MUST NOT validate when specific validationType flag is missing", () => {
121+
const fieldId = "field-id";
122+
const initialValue = "initial value";
123+
const errorMessage = "error message";
124+
125+
const validatorValidateCallback = sandbox.spy(ContainsValidator.prototype, "Validate");
126+
const fieldChildren = [<ContainsValidator value="valid" error={errorMessage} />];
127+
const formStore = new Stores.FormStore("form-id");
128+
const formStoreValidateCallback = sandbox.spy(Stores.FormStore.prototype, "Validate");
129+
const subscriber = new MySubscriber(formStore);
130+
131+
const fieldProps: FormsCoreContracts.FieldStateProps = {
132+
name: "field-name",
133+
children: fieldChildren,
134+
validationType: FieldValidationType.OnPropsChange
135+
};
136+
137+
formStore.RegisterField(fieldId, initialValue, fieldProps);
138+
// ValidateField should skip this validation because OnValueChange flag is missing
139+
subscriber.ValidateField(fieldId, initialValue, FieldValidationType.OnValueChange);
140+
141+
expect(formStoreValidateCallback.called).toBe(false);
142+
expect(validatorValidateCallback.called).toBe(false);
143+
expect(formStore.GetField(fieldId).Error).toBeUndefined();
144+
});
145+
146+
it("MUST NOT validate when field props is undefined", () => {
147+
const fieldId = "field-id";
148+
const initialValue = "initial value";
149+
150+
const validatorValidateCallback = sandbox.spy(ContainsValidator.prototype, "Validate");
151+
const formStore = new Stores.FormStore("form-id");
152+
const formStoreValidateCallback = sandbox.spy(Stores.FormStore.prototype, "Validate");
153+
const subscriber = new MySubscriber(formStore);
154+
155+
// Validation is skipped because props are undefined
156+
formStore.RegisterField(fieldId, initialValue, undefined);
157+
subscriber.ValidateField(fieldId, initialValue, FieldValidationType.OnValueChange);
158+
159+
expect(formStoreValidateCallback.called).toBe(false);
160+
expect(validatorValidateCallback.called).toBe(false);
161+
expect(formStore.GetField(fieldId).Error).toBeUndefined();
162+
});
132163
});

packages/simplr-validation/__tests__/subscribers/form-store-handler-subscriber.test.ts renamed to packages/simplr-validation/__tests__/subscribers/form-stores-handler-subscriber.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Stores, Actions } from "simplr-forms-core";
22
import * as Sinon from "sinon";
33

4-
import { FormStoreHandlerSubscriber } from "../../src/subscribers/form-store-handler-subscriber";
4+
import { FormStoresHandlerSubscriber } from "../../src/subscribers/form-stores-handler-subscriber";
55
import { FormStoreSubscriber } from "../../src/subscribers/form-store-subscriber";
66

77
const { FSHContainer } = Stores;
88

9-
class FormStoreHandlerSubscriberTest extends FormStoreHandlerSubscriber {
9+
class FormStoreHandlerSubscriberTest extends FormStoresHandlerSubscriber {
1010
public get TestFormStoresSubscribers() {
1111
return this.FormStoresSubscribers;
1212
}
@@ -16,21 +16,21 @@ let sandbox: Sinon.SinonSandbox;
1616
describe("FormStoreHandlerSubscriber", () => {
1717
beforeEach(() => {
1818
sandbox = Sinon.sandbox.create();
19-
FSHContainer.SetFormStoresHandler(new Stores.FormStoresHandlerClass());
19+
FSHContainer.SetFormStoresHandler(new Stores.FormStoresHandler());
2020
});
2121

2222
afterEach(function () {
2323
sandbox.restore();
2424
});
2525

2626
it("add listeners on form stores handler", () => {
27-
const callback = sandbox.spy(Stores.FormStoresHandlerClass.prototype, "addListener");
27+
const callback = sandbox.spy(Stores.FormStoresHandler.prototype, "addListener");
2828
const formStoreHandler = FSHContainer.FormStoresHandler;
2929

3030
expect(formStoreHandler.listeners(Actions.FormRegistered).length).toBe(0);
3131
expect(formStoreHandler.listeners(Actions.FormUnregistered).length).toBe(0);
3232

33-
new FormStoreHandlerSubscriber(FSHContainer);
33+
new FormStoresHandlerSubscriber(FSHContainer);
3434

3535
expect(callback.called).toBe(true);
3636

@@ -55,7 +55,7 @@ describe("FormStoreHandlerSubscriber", () => {
5555
it("remove FormStoreSubscriber and it listeners when form unregisters", () => {
5656
const formId = "form-id";
5757
const callback = sandbox.spy(FormStoreSubscriber.prototype, "RemoveFormListeners");
58-
new FormStoreHandlerSubscriber(FSHContainer);
58+
new FormStoresHandlerSubscriber(FSHContainer);
5959
const formStoreHandler = FSHContainer.FormStoresHandler;
6060

6161
formStoreHandler.RegisterForm(formId);

packages/simplr-validation/__tests__/validation.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ it("Validate value without errors", async (done) => {
77
const errorMessage = "error message";
88
const validValue = "p-ok";
99
const children = [
10-
<ContainsValidator value="ok" errorMessage={errorMessage} />,
10+
<ContainsValidator value="ok" error={errorMessage} />,
1111
<input type="text" />
1212
];
1313
const validationPromise = Validation.Validate(children, validValue);
@@ -31,7 +31,7 @@ it("Validate value with error", async (done) => {
3131
const errorMessage = "error message";
3232
const invvalidValue = "invalid-value";
3333
const children = [
34-
<ContainsValidator value="ok" errorMessage={errorMessage} />,
34+
<ContainsValidator value="ok" error={errorMessage} />,
3535
<input type="text" />
3636
];
3737
const validationPromise = Validation.Validate(children, invvalidValue);

packages/simplr-validation/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@types/react": "^15.0.21",
2929
"@types/react-dom": "^0.14.23",
3030
"@types/validator": "^6.2.0",
31+
"action-emitter": "^0.2.1",
3132
"immutable": "^3.8.1",
3233
"react": "^15.5.4",
3334
"simplr-forms-core": "4.0.0-pre-alpha.4",

packages/simplr-validation/src/abstractions/base-validator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as React from "react";
22
import { Contracts as FormsCoreContracts } from "simplr-forms-core";
33

4-
import { Validator, ValidationResult } from "../contracts";
4+
import { Validator, ValidationResult, ValidationError } from "../contracts";
55

66
export interface ValidatorProps {
7-
errorMessage: string;
7+
error: ValidationError;
88
}
99

1010
export abstract class BaseValidator<TProps extends ValidatorProps>
@@ -33,7 +33,7 @@ export abstract class BaseValidator<TProps extends ValidatorProps>
3333
return;
3434
}
3535

36-
protected InvalidSync(error: string): string {
36+
protected InvalidSync(error: ValidationError): ValidationError {
3737
return error;
3838
}
3939

0 commit comments

Comments
 (0)