Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input specific propType source #75

Merged
merged 53 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
501eebd
create the createFormPropertySource and add it to the global property…
jspolancor Mar 9, 2022
edd65fb
Create test cases for the createFormPropertySource function
jspolancor Mar 9, 2022
c0c9adc
Update createFormPropertySource() adding 'checkbox' and 'multiple' cases
jspolancor Mar 9, 2022
70ef62f
Update createFormPropertySource() tests cases
jspolancor Mar 9, 2022
ca69777
Updates usage of the propInfo sourceName in the property sources
jspolancor Mar 10, 2022
39d16e3
Add stories for the 'form' propType.source
jspolancor Mar 10, 2022
acf4d8e
Update checkbox behavior in createFormPropertySource()
jspolancor Mar 10, 2022
76c976c
Update stories for createFormPropertySource()
jspolancor Mar 10, 2022
63f797e
Remove the file function from createFormPropertySource
jspolancor Mar 23, 2022
1f8d419
Remove lodash dependency from createFormPropertySource
jspolancor Mar 23, 2022
b8e1c50
Fix HtmlElement casting in the tests for createFormPropertySource
jspolancor Mar 23, 2022
1d2179f
Update createFormPropertySource tests to have an isolated testing for…
jspolancor Mar 23, 2022
cca2c88
Add docs for the 'form' propType source
jspolancor Mar 23, 2022
a6ede3f
Improve form styling
ThaNarie Mar 24, 2022
33ab7b4
Update checkbox test for createFormPropertySource()
jspolancor Mar 29, 2022
2a7c1ba
Update array of checkbox values test for createFormPropertySource()
jspolancor Mar 29, 2022
c2f93d9
Add play function to the FormProps story
jspolancor Mar 29, 2022
8140770
Remove the formData ref from the FormProps tests
jspolancor Mar 29, 2022
faecd70
Add a radio button to the FormProps story
jspolancor Mar 29, 2022
767a51c
Make the 'name' property for a propType source optional
jspolancor Apr 18, 2022
ecfad28
Add json extraction tests to the FormProps story
jspolancor Apr 20, 2022
ca98f43
Add an unchecked radio to the formProp stories
jspolancor Apr 20, 2022
86ed598
Fix linting issues for createFormPropertySource()
jspolancor Apr 20, 2022
3ff8250
Refactor the formdata extraction for createFormPropertySource()
jspolancor Apr 20, 2022
11b50e7
Refactor createFormPropertySource() removing the formData boolean
jspolancor Apr 20, 2022
39ca24b
update docs for the 'form' prop type
jspolancor Apr 21, 2022
bc98a1e
Refactor the createFormPropertysource removing the validations for a …
jspolancor Apr 26, 2022
4def895
Add the formData boolean to the SourceOptionForm type
jspolancor Apr 26, 2022
ddf1665
Update text inputs tests for createFormPropertySource()
jspolancor Apr 26, 2022
09b2ef8
Add test for data transformations for createFormPropertySource()
jspolancor Apr 26, 2022
8baf1c2
Update checkboxes and select tests for createFormPropertySource()
jspolancor Apr 26, 2022
2e04b21
Update multiselect tests in createFormPropertySource()
jspolancor Apr 26, 2022
bb01e49
Update form target tests for createFormPropertySource()
jspolancor Apr 26, 2022
341bcf5
Add jsdocs to the createFormPropertySource test utils
jspolancor Apr 26, 2022
76bac6c
Update docs for the 'form' source type
jspolancor Apr 26, 2022
f46deea
Add a validation to createFormPropertySource() to check if the source…
jspolancor May 4, 2022
ba2fb0f
Update types for the createFormPropertySource() test utils
jspolancor May 4, 2022
18b9c54
Update docs for form propType source
jspolancor May 4, 2022
af15b23
Reorder createFormPropertySource() code
jspolancor May 4, 2022
34a1d96
Reorder the FormData extraction code in createFormPropertySource()
jspolancor May 5, 2022
2eafaf9
Adds radio buttons tests for createFormPropertySource()
jspolancor May 17, 2022
587407f
Adds unreleased section to the changelog
jspolancor May 17, 2022
9ae0269
Creates the getPropTypeInfo() test util function
jspolancor May 18, 2022
e554f74
Merge branch 'next' into feature/input-prop-type-source
jspolancor May 18, 2022
81a35d4
Remove branch name from the unreleased section in changelog
jspolancor May 24, 2022
7f0fea7
Remove an unnecesary condition from createFormPropertySource()
jspolancor May 24, 2022
5477836
Merge branch 'feature/input-prop-type-source' of github.com:mubanjs/m…
jspolancor May 24, 2022
363a133
Remove id's from createFormPopertySource() test utils
jspolancor May 25, 2022
f8dc07b
Update array value checking for createFormPropertySource()
jspolancor May 25, 2022
a684c1f
Removes an unnecessary Array check in createFormPropertySource()
jspolancor May 31, 2022
d939976
Fix return value in createFormPropertySource() for empty values
jspolancor May 31, 2022
be934fc
Update CHANGELOG.md
ThaNarie Sep 20, 2022
a3d7bd2
Merge remote-tracking branch 'origin/next' into feature/input-prop-ty…
ThaNarie Sep 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions docs/api/props.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export type PropTypeDefinition<T = any> = {
shapeType?: Function;
sourceOptions?: {
target?: string;
type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html';
type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html' | 'form';
name?: string;
options?: {
cssPredicate?: Array<string>;
Expand Down Expand Up @@ -265,7 +265,7 @@ HTML besides the default behaviour.
```ts
declare function source(options: {
target?: string;
type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html';
type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html' | 'form';
name?: string;
options?: {
cssPredicate?: Array<string>;
Expand All @@ -276,7 +276,7 @@ declare function source(options: {
* `target?: string` – The refName (those you configure as part of the component options)
from which you want to extract this property.
Defaults to the data-component element.
* `type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html'` - The type source you want to extract.
* `type?: 'data' | 'json' | 'attr' | 'css' | 'text' | 'html' | 'form'` - The type source you want to extract.
Defaults to the `data + json + css` source (`css` only for boolean props).
* `data` – Reads the `data-attribute` from your target element.
* `json` – Reads the object key from a `<script type="application/json">` JSON block that is
Expand All @@ -290,6 +290,7 @@ declare function source(options: {
to all values.
* `text` – Reads the `textContents` from the target element.
* `html` – Reads the `innerHTML` from the target element.
* `form` – Reads the `value` from the target element when targeting a form input and `FormData` when targeting a form element
* `name?: string` – For the `data`, `json`, `attr` and `css` source types, by default it will use
the
propName for the name of the (data) attribute or class name. For situations where the name
Expand Down Expand Up @@ -530,3 +531,45 @@ defineComponent({
<span data-ref="value">12.45</span>
</div>
```
#### Use `form`
```ts
defineComponent({
name: 'my-component',
refs: {
// this is needed for the source-target
form: 'form',
email: 'email',
phone: 'phone',
},
props: {
// get the FormData from the form ref
// outputs FormData {}
form: propType.object.source(
{ target: 'form', type: 'form', formData: true},
),
// Extract the 'email' property from the form ref FormData object
// outputs "[email protected]"
form: propType.string.source(
{ target: 'form', type: 'form', name: 'email'},
),
ThaNarie marked this conversation as resolved.
Show resolved Hide resolved
jspolancor marked this conversation as resolved.
Show resolved Hide resolved
// get the value from the email ref
// outputs "[email protected]"
email: propType.string.source(
{ target: 'email', type: 'form'},
),
// conversions to Booleans, Numbers and Dates also work
// outputs "986868" (as a number)
phone: propType.number.source(
{ target: 'phone', type: 'form'},
),
},
})
```
```html
<div data-component="my-component">
<form data-ref="form">
<input type="text" data-ref="email" value="[email protected]"/>
<input type="text" data-ref="phone" value="986868"/>
</form>
</div>
```
4 changes: 3 additions & 1 deletion docs/guide/props.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ specify exactly what values you want to extract from where.

* The `attr` source is similar to the `data` source, but uses normal attributes to extract data
from, and also allows conversion.

* The `form` source allow you to extract _Input state_ from form elements, when targeting an input it will extract the value, when targeting a form it will extract the FormData Object. It allows for value conversion into basic data types as well.
ThaNarie marked this conversation as resolved.
Show resolved Hide resolved
jspolancor marked this conversation as resolved.
Show resolved Hide resolved

_Input state_ can be extracted using the `attr` binding if needed, but most often you will end
Remember, the `form` binding will extract the value from form inputs, but most often you will end
up using two-way bindings to manage syncing up these values with the internal component state.

### Parent components
Expand Down
11 changes: 10 additions & 1 deletion src/lib/props/propDefinitions.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { RefElementType } from '../refs/refDefinitions.types';
export type SourceOption =
| SourceOptionCss
| SourceOptionHtmlText
| SourceOptionForm
| {
type?: 'data' | 'json' | 'attr';
target?: string;
Expand All @@ -19,6 +20,13 @@ export type SourceOptionHtmlText = {
target?: string;
};

export type SourceOptionForm = {
type: 'form';
target?: string;
name?: string;
formData?: boolean;
};

export type SourceOptionCss = {
type: 'css';
target?: string;
Expand Down Expand Up @@ -55,7 +63,8 @@ export type PropTypeInfo<T = any> = Pick<
name: string;
target: RefElementType | undefined;
} & Pick<SourceOption, 'type'> &
Pick<SourceOptionCss, 'options'>;
Pick<SourceOptionCss, 'options'> &
Pick<SourceOptionForm, 'formData'>;
};

// type OptionalPropertyKeys<T> = {
Expand Down
235 changes: 235 additions & 0 deletions src/lib/props/property-sources/createFormPropertySource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import type { PropTypeInfo } from '../propDefinitions.types';
import { createFormPropertySource } from './createFormPropertySource';
import { getFullPropTypeInfo } from './createFormPropertySource.testutils';

describe('createFormPropertySource', () => {
describe('function itself', () => {
it('should create without errors', () => {
expect(createFormPropertySource).not.toThrow();
});
it('should allow calling the created source without errors', () => {
expect(createFormPropertySource()).not.toThrow();
});
});

describe('hasProp', () => {
it('should return false if the type is "Function"', () => {
const form = document.createElement('form');
const functionPropInfo: PropTypeInfo = {
name: 'email',
type: Function,
source: {
name: 'email',
target: form,
type: 'form',
},
};
expect(createFormPropertySource()(form).hasProp(functionPropInfo)).toBe(false);
});
it('should return true if the type is different than "Function"', () => {
const form = document.createElement('form');
const validPropInfoTypes = [Number, String, Boolean, Date, Array, Object];
const propInfos: Array<PropTypeInfo> = validPropInfoTypes.map((type) => ({
name: 'foo',
type,
source: {
name: 'foo',
target: form,
type: 'form',
},
}));
propInfos.forEach((propInfo) => {
expect(createFormPropertySource()(form).hasProp(propInfo)).toBe(true);
});
});
});

describe('getProp', () => {
describe('Text inputs', () => {
const form = document.createElement('form');
form.innerHTML = `
<input id="email" type="email" name="email" value="[email protected]"/>
<input id="password" type="password" name="password" value="123456"/>
<textarea id="description" name="description">lorem ipsum</textarea>
`;
const email = getFullPropTypeInfo('email', form);
const password = getFullPropTypeInfo('password', form);
const description = getFullPropTypeInfo('description', form);
const formSource = createFormPropertySource()(form);

describe('type String with an input target', () => {
it('Should return the input value if the target is a text input', () => {
expect(formSource.getProp(email.string.asInput)).toBe('[email protected]');
expect(formSource.getProp(password.string.asInput)).toBe('123456');
expect(formSource.getProp(description.string.asInput)).toBe('lorem ipsum');
});
});

describe('type String with a form target', () => {
it('Should return the child input value if the target form', () => {
expect(formSource.getProp(email.string.asForm)).toBe('[email protected]');
expect(formSource.getProp(password.string.asForm)).toBe('123456');
expect(formSource.getProp(description.string.asForm)).toBe('lorem ipsum');
});
});
});

describe('Non String text inputs', () => {
const form = document.createElement('form');
form.innerHTML = `
<input type="text" name="age" id="age" value="32"/>
<input type="text" name="height" id="height" value="1.70"/>
<input type="text" name="terms" id="terms" value="true"/>
<input type="text" name="kids" id="kids" value="false"/>
<input type="text" name="birthday" id="birthday" value="2022/06/06"/>
<input type="text" name="pets" id="pets" value='["Armin", "Trico"]'/>
<input type="text" name="area" id="area" value='{ "zip": "110010", "latlong": 1293847}'/>
`;

const age = getFullPropTypeInfo('age', form);
const height = getFullPropTypeInfo('height', form);
const terms = getFullPropTypeInfo('terms', form);
const kids = getFullPropTypeInfo('kids', form);
const birthday = getFullPropTypeInfo('birthday', form);
const pets = getFullPropTypeInfo('pets', form);
const area = getFullPropTypeInfo('area', form);
const source = createFormPropertySource()(form);

it('Should return transformed input values for all possible types', () => {
expect(source.getProp(age.number.asInput)).toBe(32);
expect(source.getProp(height.number.asInput)).toBe(1.7);
expect(source.getProp(terms.boolean.asInput)).toBe(true);
expect(source.getProp(kids.boolean.asInput)).toBe(false);
expect(source.getProp(birthday.date.asInput)).toBeInstanceOf(Date);
expect(source.getProp(pets.array.asInput)).toEqual(['Armin', 'Trico']);
expect(source.getProp(area.object.asInput)).toEqual({ zip: '110010', latlong: 1293847 });
});

it('Should return the same transformed input values when passing an input element target and a form element target', () => {
expect(source.getProp(age.number.asInput)).toBe(source.getProp(age.number.asForm));
expect(source.getProp(age.number.asInput)).toBe(source.getProp(age.number.asForm));
expect(source.getProp(height.number.asInput)).toBe(source.getProp(height.number.asForm));
expect(source.getProp(terms.boolean.asInput)).toBe(source.getProp(terms.boolean.asForm));
expect(source.getProp(kids.boolean.asInput)).toBe(source.getProp(kids.boolean.asForm));
expect(source.getProp(birthday.date.asInput)).toEqual(source.getProp(birthday.date.asForm));
expect(source.getProp(pets.array.asInput)).toEqual(source.getProp(pets.array.asForm));
expect(source.getProp(area.object.asInput)).toEqual(source.getProp(area.object.asForm));
});
});

describe('checkbox', () => {
const form = document.createElement('form');
form.innerHTML = `
<input id="onBoolean" name="onBoolean" type="checkbox" checked/>
<input id="offBoolean" name="offBoolean" type="checkbox" />
<input id="onString" name="onString" type="checkbox" checked/>
<input id="offString" name="offString" type="checkbox" />
<input id="onStringValue" name="onStringValue" type="checkbox" value="foo" checked />
<input id="offStringValue" name="offStringValue" type="checkbox" value="foo" />
`;

const onBoolean = getFullPropTypeInfo('onBoolean', form);
const offBoolean = getFullPropTypeInfo('offBoolean', form);
const onString = getFullPropTypeInfo('onString', form);
const offString = getFullPropTypeInfo('offString', form);
const onStringValue = getFullPropTypeInfo('onStringValue', form);
const offStringValue = getFullPropTypeInfo('offStringValue', form);

it('Should return the checked value if the target is a checkbox and the propType is boolean', () => {
expect(createFormPropertySource()(form).getProp(onBoolean.boolean.asInput)).toBe(true);
expect(createFormPropertySource()(form).getProp(offBoolean.boolean.asInput)).toBe(false);
});

it('Should return the input value if the target is a checked checkbox and the propType is not boolean', () => {
expect(createFormPropertySource()(form).getProp(onString.string.asInput)).toBe('on');
expect(createFormPropertySource()(form).getProp(offString.string.asInput)).toBe(undefined);
expect(createFormPropertySource()(form).getProp(onStringValue.string.asInput)).toBe('foo');
expect(createFormPropertySource()(form).getProp(offStringValue.string.asInput)).toBe(
undefined,
);
});

it('Should return an array of strings if the target is a form with multiple checkboxes with the same name and the propType is Array', () => {
const checkboxesForm = document.createElement('form');
checkboxesForm.innerHTML = `
<input name="choices" type="checkbox" value="apple" checked/>
<input name="choices" type="checkbox" value="banana" checked />
<input name="choices" type="checkbox" value="peach" />
<input name="choices" type="checkbox" />
`;
const choices = getFullPropTypeInfo('checkbox', checkboxesForm, 'choices');

expect(createFormPropertySource()(checkboxesForm).getProp(choices.array.asForm)).toEqual([
'apple',
'banana',
]);
});
});

describe('select', () => {
it('Should return the select value if the target is a select that is not multiple', () => {
const form = document.createElement('form');
form.innerHTML = `
<select id="preference" name="preference">
<option value="foo" selected>foo</option>
<option value="bar">bar</option>
</select>
<select id="preferenceBoolean" name="preferenceBoolean">
<option value="true" selected>foo</option>
<option value="false">bar</option>
</select>
`;
const select = getFullPropTypeInfo('select', form, 'preference');
const selectBoolean = getFullPropTypeInfo('select', form, 'preferenceBoolean');

expect(createFormPropertySource()(form).getProp(selectBoolean.boolean.asInput)).toBe(true);
expect(createFormPropertySource()(form).getProp(selectBoolean.boolean.asForm)).toBe(true);
expect(createFormPropertySource()(form).getProp(select.string.asInput)).toBe('foo');
expect(createFormPropertySource()(form).getProp(select.string.asForm)).toBe('foo');
});

it('Should return an array of strings if the target is a multiselect', () => {
const form = document.createElement('form');
form.innerHTML = `
<select id="candidates" name="candidates" multiple>
<option value="foo" selected>foo</option>
<option value="bar" selected>bar</option>
</select>
`;
const candidates = getFullPropTypeInfo('multiselect', form, 'candidates');

expect(createFormPropertySource()(form).getProp(candidates.array.asForm)).toEqual([
'foo',
'bar',
]);
expect(createFormPropertySource()(form).getProp(candidates.array.asInput)).toEqual([
'foo',
'bar',
]);
});
});

describe('form', () => {
it('Should return undefined when passing a form as target and an unmatching child input', () => {
const form = document.createElement('form');
const unmatchingName = getFullPropTypeInfo('myForm', form, 'nomatch');

expect(createFormPropertySource()(form).getProp(unmatchingName.string.asForm)).toBe(
undefined,
);
});

it('Should return FormData object when using type "Object" and formData: true', () => {
ThaNarie marked this conversation as resolved.
Show resolved Hide resolved
jspolancor marked this conversation as resolved.
Show resolved Hide resolved
const form = document.createElement('form');
form.innerHTML = `
<input id="email" type="email" name="email" value="[email protected]"/>
`;
const validForm = getFullPropTypeInfo('validForm', form, undefined, true);
const formData = createFormPropertySource()(form).getProp(validForm.object.asForm);

expect(formData).toBeInstanceOf(FormData);
expect((formData as FormData).get('email')).toBe('[email protected]');
});
});
});
});
Loading