Include all tests for the primary functions as well as the utilities.master
@@ -55,7 +55,7 @@ describe('checkbox', () => { | |||||
} | } | ||||
}); | }); | ||||
}); | }); | ||||
}) | |||||
}); | |||||
describe('checked', () => { | describe('checked', () => { | ||||
beforeEach(utils.setup(` | beforeEach(utils.setup(` | ||||
@@ -93,7 +93,6 @@ describe('checkbox', () => { | |||||
}); | }); | ||||
}); | }); | ||||
describe('duplicate', () => { | describe('duplicate', () => { | ||||
beforeEach(utils.setup(` | beforeEach(utils.setup(` | ||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
@@ -165,4 +164,221 @@ describe('checkbox', () => { | |||||
}); | }); | ||||
}); | }); | ||||
}); | }); | ||||
describe('setting values', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Checkbox/Setting Values</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<input type="checkbox" name="enabled" /> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should check for boolean "true"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: true, }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).toBe('on'); | |||||
}, | |||||
expectedStaticValue: 'enabled=on', | |||||
}); | |||||
}); | |||||
it('should check for string "true"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'true', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).toBe('on'); | |||||
}, | |||||
expectedStaticValue: 'enabled=on', | |||||
}); | |||||
}); | |||||
it('should check for string "yes"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'yes', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).toBe('on'); | |||||
}, | |||||
expectedStaticValue: 'enabled=on', | |||||
}); | |||||
}); | |||||
it('should check for string "on"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'on', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).toBe('on'); | |||||
}, | |||||
expectedStaticValue: 'enabled=on', | |||||
}); | |||||
}); | |||||
it('should uncheck for boolean "false"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: false, }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should uncheck for string "false"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'false', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should uncheck for string "no"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'no', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should uncheck for string "off"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'off', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should check for number "1"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 1, }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).toBe('on'); | |||||
}, | |||||
expectedStaticValue: 'enabled=on', | |||||
}); | |||||
}); | |||||
it('should check for string "1"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: '1', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).toBe('on'); | |||||
}, | |||||
expectedStaticValue: 'enabled=on', | |||||
}); | |||||
}); | |||||
it('should uncheck for number "0"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 0, }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should uncheck for string "0"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: '0', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should uncheck for object "null"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: null, }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
it('should uncheck for string "null"', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { enabled: 'null', }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter }) | |||||
expect(values['enabled']).not.toBe('on'); | |||||
}, | |||||
expectedStaticValue: '', | |||||
}); | |||||
}); | |||||
}); | |||||
}); | }); |
@@ -38,6 +38,19 @@ describe('date', () => { | |||||
}, | }, | ||||
}); | }); | ||||
}); | }); | ||||
it('should enable Date representation', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter, forceDateValues: true }); | |||||
// somehow, checking instanceof Date fails here, because we're using an artificial date | |||||
// object? | |||||
const testDate = new Date(values.hello as Date); | |||||
expect((values.hello as Date).getTime()).toBe(testDate.getTime()); | |||||
}, | |||||
}); | |||||
}); | |||||
}) | }) | ||||
describe('disabled', () => { | describe('disabled', () => { | ||||
@@ -38,6 +38,19 @@ describe('date', () => { | |||||
}, | }, | ||||
}); | }); | ||||
}); | }); | ||||
it('should enable Date representation', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter, forceDateValues: true }); | |||||
// somehow, checking instanceof Date fails here, because we're using an artificial date | |||||
// object? | |||||
const testDate = new Date(values.hello as Date); | |||||
expect((values.hello as Date).getTime()).toBe(testDate.getTime()); | |||||
}, | |||||
}); | |||||
}); | |||||
}) | }) | ||||
describe('disabled', () => { | describe('disabled', () => { | ||||
@@ -1,4 +1,10 @@ | |||||
import getFormValuesDeprecated, { getFormValues, setFormValues } from '../../src'; | |||||
import getFormValuesDeprecated, { | |||||
getFormValues, | |||||
setFormValues, | |||||
isFieldElement, | |||||
isElementValueIncludedInFormSubmit, | |||||
getValue, | |||||
} from '../../src'; | |||||
import * as utils from '../utils' | import * as utils from '../utils' | ||||
describe('misc', () => { | describe('misc', () => { | ||||
@@ -177,7 +183,213 @@ describe('misc', () => { | |||||
}, | }, | ||||
}); | }); | ||||
}); | }); | ||||
}) | |||||
}); | |||||
describe('utilities', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Misc/Utilities</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<input id="input" type="text" name="foobar" /> | |||||
<input id="notField" type="text" /> | |||||
<input id="disabled" disabled type="text" name="disabled" /> | |||||
<meter id="meter" min="1" max="10" value="5" /> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)); | |||||
it('should check for valid field elements value', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const meter = document.getElementById('meter'); | |||||
expect(getValue(meter)).toBe(5); | |||||
}, | |||||
}); | |||||
}); | |||||
it('should check for invalid field elements value', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
expect(getValue(document.body)).toBe(null); | |||||
}, | |||||
}); | |||||
}); | |||||
it('should check for elements as included fields', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const input = document.getElementById('input'); | |||||
expect(isElementValueIncludedInFormSubmit(input)).toBe(true); | |||||
}, | |||||
}); | |||||
}); | |||||
it('should check for elements as excluded fields', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const notField = document.getElementById('notField'); | |||||
expect(isElementValueIncludedInFormSubmit(notField)).toBe(false); | |||||
const disabled = document.getElementById('disabled'); | |||||
expect(isElementValueIncludedInFormSubmit(disabled)).toBe(false); | |||||
const meter = document.getElementById('meter'); | |||||
expect(isElementValueIncludedInFormSubmit(meter)).toBe(false); | |||||
}, | |||||
}); | |||||
}); | |||||
it('should check for elements as valid for fields', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const input = document.getElementById('input'); | |||||
expect(isFieldElement(input)).toBe(true); | |||||
const disabled = document.getElementById('disabled'); | |||||
expect(isFieldElement(disabled)).toBe(true); | |||||
}, | |||||
}); | |||||
}); | |||||
it('should check for elements as invalid for fields', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const meter = document.getElementById('meter'); | |||||
expect(isFieldElement(meter)).toBe(false); | |||||
const notField = document.getElementById('notField'); | |||||
expect(isFieldElement(notField)).toBe(false); | |||||
}, | |||||
}); | |||||
}); | |||||
}); | |||||
describe('setting values', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Misc/Blank</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<input type="text" name="foobar" /> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should parse string values for setFormValues', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
let isThrown = false; | |||||
try { | |||||
setFormValues(form, 'foobar=baz'); | |||||
} catch (e) { | |||||
isThrown = true; | |||||
} | |||||
expect(isThrown).toBe(false); | |||||
expect(getFormValues(form)).toEqual({ foobar: 'baz', }); | |||||
}, | |||||
}) | |||||
}); | |||||
it('should parse entries values for setFormValues', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
let isThrown = false; | |||||
try { | |||||
setFormValues(form, [['foobar', 'baz']]); | |||||
} catch (e) { | |||||
isThrown = true; | |||||
} | |||||
expect(isThrown).toBe(false); | |||||
expect(getFormValues(form)).toEqual({ foobar: 'baz', }); | |||||
}, | |||||
}) | |||||
}); | |||||
it('should parse URLSearchParams values for setFormValues', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
let isThrown = false; | |||||
try { | |||||
setFormValues(form, new URLSearchParams('foobar=baz')); | |||||
} catch (e) { | |||||
isThrown = true; | |||||
} | |||||
expect(isThrown).toBe(false); | |||||
expect(getFormValues(form)).toEqual({ foobar: 'baz', }); | |||||
}, | |||||
}) | |||||
}); | |||||
it('should parse object values for setFormValues', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
let isThrown = false; | |||||
try { | |||||
setFormValues(form, { foobar: 'baz', }); | |||||
} catch (e) { | |||||
isThrown = true; | |||||
} | |||||
expect(isThrown).toBe(false); | |||||
expect(getFormValues(form)).toEqual({ foobar: 'baz', }); | |||||
}, | |||||
}) | |||||
}); | |||||
}); | |||||
describe('duplicates', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Misc/Blank</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<input type="text" name="foobar" /> | |||||
<input type="text" name="foobar" /> | |||||
<input type="text" name="foobar" /> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)); | |||||
it('should parse duplicates correctly', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { foobar: ['foo', 'bar', 'baz']}) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
expect(getFormValues(form)).toEqual({ foobar: ['foo', 'bar', 'baz'], }); | |||||
}, | |||||
}) | |||||
}); | |||||
}); | |||||
describe('blank', () => { | describe('blank', () => { | ||||
beforeEach(utils.setup(` | beforeEach(utils.setup(` | ||||
@@ -38,6 +38,19 @@ describe('month', () => { | |||||
}, | }, | ||||
}); | }); | ||||
}); | }); | ||||
it('should enable Date representation', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const values = getFormValues(form, { submitter, forceDateValues: true }); | |||||
// somehow, checking instanceof Date fails here, because we're using an artificial date | |||||
// object? | |||||
const testDate = new Date(values.hello as Date); | |||||
expect((values.hello as Date).getTime()).toBe(testDate.getTime()); | |||||
}, | |||||
}); | |||||
}); | |||||
}) | }) | ||||
describe('disabled', () => { | describe('disabled', () => { | ||||
@@ -1,4 +1,4 @@ | |||||
import { getFormValues } from '../../src' | |||||
import { getFormValues, setFormValues } from '../../src'; | |||||
import * as utils from '../utils' | import * as utils from '../utils' | ||||
describe('select', () => { | describe('select', () => { | ||||
@@ -41,6 +41,107 @@ describe('select', () => { | |||||
expectedStaticValue: 'hello=Bar&hello=Quux' | expectedStaticValue: 'hello=Bar&hello=Quux' | ||||
}); | }); | ||||
}); | }); | ||||
it('should set values correctly', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { hello: ['Foo', 'Baz'] }); | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Foo&hello=Baz' | |||||
}); | |||||
}); | |||||
}) | |||||
describe('multiple duplicate', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Select/Multiple Duplicate</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<select name="hello" multiple> | |||||
<option>Foo</option> | |||||
<option selected>Bar</option> | |||||
<option>Baz</option> | |||||
<option selected>Quux</option> | |||||
</select> | |||||
<select name="hello" multiple> | |||||
<option>Chocolate</option> | |||||
<option selected>Mango</option> | |||||
<option>Vanilla</option> | |||||
<option selected>Ube</option> | |||||
</select> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should have multiple form values on a single field', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Bar&hello=Quux&hello=Mango&hello=Ube' | |||||
}); | |||||
}); | |||||
it('should set multiple form values across all selects', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { hello: ['Foo', 'Baz', 'Chocolate', 'Vanilla'] }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Foo&hello=Baz&hello=Chocolate&hello=Vanilla' | |||||
}); | |||||
}); | |||||
it('should set multiple form values on each corresponding select element', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { hello: [['Foo', 'Baz', 'Chocolate'], ['Vanilla']] }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Foo&hello=Baz&hello=Vanilla' | |||||
}); | |||||
}); | |||||
}) | }) | ||||
describe('single', () => { | describe('single', () => { | ||||
@@ -84,4 +185,88 @@ describe('select', () => { | |||||
}); | }); | ||||
}); | }); | ||||
}) | }) | ||||
describe('single duplicate', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Select/Single Duplicate</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<select name="hello"> | |||||
<option>Foo</option> | |||||
<option selected>Bar</option> | |||||
<option>Baz</option> | |||||
<option>Quux</option> | |||||
</select> | |||||
<select name="hello"> | |||||
<option>Chocolate</option> | |||||
<option>Mango</option> | |||||
<option>Vanilla</option> | |||||
<option selected>Ube</option> | |||||
<option>Foo</option> | |||||
</select> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should have multiple form values on a single field', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Bar&hello=Ube' | |||||
}); | |||||
}); | |||||
it('should set multiple form values across all selects', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { hello: ['Foo', 'Chocolate'] }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Foo&hello=Chocolate' | |||||
}); | |||||
}); | |||||
it('should set multiple form values on each corresponding select element', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { hello: ['Foo', 'Ube'] }) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: 'hello=Foo&hello=Ube' | |||||
}); | |||||
}); | |||||
}) | |||||
}) | }) |
@@ -0,0 +1,265 @@ | |||||
import { getFormValues, setFormValues } from '../../src'; | |||||
import * as utils from '../utils' | |||||
describe('time', () => { | |||||
describe('basic', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Time/Basic</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<input type="time" name="hello" value="13:37" /> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should have single form value', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: { | |||||
hello: '13:37', | |||||
}, | |||||
}); | |||||
}); | |||||
}) | |||||
describe('disabled', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Time/Disabled</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<input | |||||
type="time" name="hello" value="13:37" | |||||
disabled | |||||
/> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should have blank form value', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: {}, | |||||
}); | |||||
}); | |||||
}) | |||||
describe('outside', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Time/Outside</title> | |||||
</head> | |||||
<body> | |||||
<form id="form"> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<input type="time" name="hello" value="13:37" form="form" /> | |||||
</label> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should have single form value', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: { | |||||
hello: '13:37', | |||||
}, | |||||
}); | |||||
}); | |||||
}); | |||||
describe('readonly', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Time/Readonly</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<input | |||||
type="time" name="hello" value="13:37" | |||||
readonly | |||||
/> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)) | |||||
it('should have single form value', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: { | |||||
hello: '13:37', | |||||
}, | |||||
}); | |||||
}); | |||||
}); | |||||
describe('programmatic value setting', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Time/Programmatic Value Setting</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello</span> | |||||
<input type="time" name="hello" /> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)); | |||||
it('should have form values set', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { hello: '13:37', }) | |||||
}, | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: { | |||||
hello: '13:37', | |||||
}, | |||||
}); | |||||
}); | |||||
}); | |||||
describe('duplicate', () => { | |||||
beforeEach(utils.setup(` | |||||
<!DOCTYPE html> | |||||
<html lang="en-PH"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Time/Duplicate</title> | |||||
</head> | |||||
<body> | |||||
<form> | |||||
<label> | |||||
<span>Hello 1</span> | |||||
<input id="hello1" type="time" value="13:37" name="hello"/> | |||||
</label> | |||||
<label> | |||||
<span>Hello 2</span> | |||||
<input id="hello2" type="time" value="06:09" name="hello"/> | |||||
</label> | |||||
<button type="submit">Submit</button> | |||||
</form> | |||||
</body> | |||||
</html> | |||||
`)); | |||||
it('should get both values', () => { | |||||
utils.test({ | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: { | |||||
hello: ['13:37', '06:09'], | |||||
}, | |||||
}); | |||||
}); | |||||
it('should set both values', () => { | |||||
utils.test({ | |||||
preAction: (form: HTMLFormElement) => { | |||||
setFormValues(form, { | |||||
hello: ['04:20', '05:30'], | |||||
}) | |||||
}, | |||||
action: (cy: any) => cy.get('[type="submit"]'), | |||||
test: (form: HTMLFormElement, submitter: any, search: any) => { | |||||
const before = utils.makeSearchParams(getFormValues(form, { submitter })) | |||||
.toString(); | |||||
const after = utils.makeSearchParams(search) | |||||
.toString(); | |||||
expect(before) | |||||
.toEqual(after); | |||||
}, | |||||
expectedStaticValue: { | |||||
hello: ['04:20', '05:30'], | |||||
}, | |||||
}); | |||||
}); | |||||
}); | |||||
}) |
@@ -44,8 +44,9 @@ const FORM_FIELD_INPUT_EXCLUDED_TYPES = ['submit', 'reset'] as const; | |||||
/** | /** | ||||
* Checks if an element can hold a custom (user-inputted) field value. | * Checks if an element can hold a custom (user-inputted) field value. | ||||
* @param el - The element. | * @param el - The element. | ||||
* @returns Value determining if an element can hold a custom (user-inputted) field value. | |||||
*/ | */ | ||||
export const isFormFieldElement = (el: HTMLElement) => { | |||||
export const isFieldElement = (el: HTMLElement) => { | |||||
const { tagName } = el; | const { tagName } = el; | ||||
if (FORM_FIELD_ELEMENT_TAG_NAMES.includes(tagName as typeof FORM_FIELD_ELEMENT_TAG_NAMES[0])) { | if (FORM_FIELD_ELEMENT_TAG_NAMES.includes(tagName as typeof FORM_FIELD_ELEMENT_TAG_NAMES[0])) { | ||||
return true; | return true; | ||||
@@ -92,15 +93,15 @@ const getTextAreaFieldValue = ( | |||||
* @param textareaEl - The element. | * @param textareaEl - The element. | ||||
* @param value - Value of the textarea element. | * @param value - Value of the textarea element. | ||||
* @param nthOfName - What order is this field in with respect to fields of the same name? | * @param nthOfName - What order is this field in with respect to fields of the same name? | ||||
* @param totalOfName - How many fields with the same name are in the form? | |||||
* @param elementsOfSameName - How many fields with the same name are in the form? | |||||
*/ | */ | ||||
const setTextAreaFieldValue = ( | const setTextAreaFieldValue = ( | ||||
textareaEl: HTMLTextAreaElement, | textareaEl: HTMLTextAreaElement, | ||||
value: unknown, | value: unknown, | ||||
nthOfName: number, | nthOfName: number, | ||||
totalOfName: number, | |||||
elementsOfSameName: HTMLTextAreaElement[], | |||||
) => { | ) => { | ||||
if (Array.isArray(value) && totalOfName > 1) { | |||||
if (Array.isArray(value) && elementsOfSameName.length > 1) { | |||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
textareaEl.value = value[nthOfName]; | textareaEl.value = value[nthOfName]; | ||||
return; | return; | ||||
@@ -128,19 +129,54 @@ const getSelectFieldValue = ( | |||||
* Sets the value of a `<select>` element. | * Sets the value of a `<select>` element. | ||||
* @param selectEl - The element. | * @param selectEl - The element. | ||||
* @param value - Value of the select element. | * @param value - Value of the select element. | ||||
* @param nthOfName - What order is this field in with respect to fields of the same name? | |||||
* @param elementsOfSameName - How many fields with the same name are in the form? | |||||
*/ | */ | ||||
const setSelectFieldValue = (selectEl: HTMLSelectElement, value: unknown) => { | |||||
Array.from(selectEl.options) | |||||
.filter((o) => { | |||||
if (Array.isArray(value)) { | |||||
return (value as string[]).includes(o.value); | |||||
} | |||||
return o.value === value; | |||||
}) | |||||
.forEach((el) => { | |||||
const setSelectFieldValue = ( | |||||
selectEl: HTMLSelectElement, | |||||
value: unknown, | |||||
nthOfName: number, | |||||
elementsOfSameName: HTMLSelectElement[], | |||||
) => { | |||||
if (elementsOfSameName.length > 1) { | |||||
const valueArray = value as unknown[]; | |||||
const valueArrayDepth = valueArray.every((v) => Array.isArray(v)) ? 2 : 1; | |||||
if (valueArrayDepth > 1) { | |||||
// We check if values are [['foo', 'bar], ['baz', 'quux'], 'single value] | |||||
// If this happens, all values must correspond to a <select multiple> element. | |||||
const currentValue = valueArray[nthOfName] as string[]; | |||||
Array.from(selectEl.options).forEach((el) => { | |||||
// eslint-disable-next-line no-param-reassign | |||||
el.selected = currentValue.includes(el.value); | |||||
}); | |||||
return; | |||||
} | |||||
// Else we're just checking if these values are in the value array provided. | |||||
// They will apply across all select elements. | |||||
if (elementsOfSameName.some((el) => el.multiple)) { | |||||
Array.from(selectEl.options).forEach((el) => { | |||||
// eslint-disable-next-line no-param-reassign | |||||
el.selected = (value as string[]).includes(el.value); | |||||
}); | |||||
return; | |||||
} | |||||
Array.from(selectEl.options).forEach((el) => { | |||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
el.selected = true; | |||||
el.selected = el.value === (value as string[])[nthOfName]; | |||||
}); | }); | ||||
return; | |||||
} | |||||
Array.from(selectEl.options).forEach((el) => { | |||||
// eslint-disable-next-line no-param-reassign | |||||
el.selected = Array.isArray(value) | |||||
? (value as string[]).includes(el.value) | |||||
: el.value === value; | |||||
}); | |||||
}; | }; | ||||
/** | /** | ||||
@@ -248,6 +284,45 @@ const getInputCheckboxFieldValue = ( | |||||
return null; | return null; | ||||
}; | }; | ||||
const parseBooleanValues = (value: unknown) => { | |||||
if (typeof value === 'boolean') { | |||||
return value; | |||||
} | |||||
if (typeof value === 'string') { | |||||
const normalizedValue = value.toLowerCase(); | |||||
if (INPUT_CHECKBOX_FALSY_VALUES.includes( | |||||
normalizedValue as typeof INPUT_CHECKBOX_FALSY_VALUES[0], | |||||
)) { | |||||
return false; | |||||
} | |||||
if (INPUT_CHECKBOX_TRUTHY_VALUES.includes( | |||||
normalizedValue as typeof INPUT_CHECKBOX_TRUTHY_VALUES[0], | |||||
)) { | |||||
return true; | |||||
} | |||||
} | |||||
if (typeof value === 'number') { | |||||
if (value === 0) { | |||||
return false; | |||||
} | |||||
if (value === 1) { | |||||
return true; | |||||
} | |||||
} | |||||
if (typeof value === 'object') { | |||||
if (value === null) { | |||||
return false; | |||||
} | |||||
} | |||||
return undefined; | |||||
}; | |||||
/** | /** | ||||
* Sets the value of an `<input type="checkbox">` element. | * Sets the value of an `<input type="checkbox">` element. | ||||
* @param inputEl - The element. | * @param inputEl - The element. | ||||
@@ -269,26 +344,10 @@ const setInputCheckboxFieldValue = ( | |||||
return; | return; | ||||
} | } | ||||
if ( | |||||
INPUT_CHECKBOX_FALSY_VALUES.includes( | |||||
(value as string).toLowerCase() as typeof INPUT_CHECKBOX_FALSY_VALUES[0], | |||||
) | |||||
|| !value | |||||
) { | |||||
// eslint-disable-next-line no-param-reassign | |||||
inputEl.checked = false; | |||||
return; | |||||
} | |||||
if ( | |||||
INPUT_CHECKBOX_TRUTHY_VALUES.includes( | |||||
(value as string).toLowerCase() as typeof INPUT_CHECKBOX_TRUTHY_VALUES[0], | |||||
) | |||||
|| value === true | |||||
|| value === 1 | |||||
) { | |||||
const newValue = parseBooleanValues(value); | |||||
if (typeof newValue === 'boolean') { | |||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
inputEl.checked = true; | |||||
inputEl.checked = newValue; | |||||
} | } | ||||
}; | }; | ||||
@@ -324,9 +383,6 @@ const getInputFileFieldValue = ( | |||||
options = {} as GetInputFileFieldValueOptions, | options = {} as GetInputFileFieldValueOptions, | ||||
) => { | ) => { | ||||
const { files } = inputEl; | const { files } = inputEl; | ||||
if ((files as unknown) === null) { | |||||
return null; | |||||
} | |||||
if (options.getFileObjects) { | if (options.getFileObjects) { | ||||
return files; | return files; | ||||
} | } | ||||
@@ -395,17 +451,17 @@ const getInputNumericFieldValue = ( | |||||
* @param inputEl - The element. | * @param inputEl - The element. | ||||
* @param value - Value of the input element. | * @param value - Value of the input element. | ||||
* @param nthOfName - What order is this field in with respect to fields of the same name? | * @param nthOfName - What order is this field in with respect to fields of the same name? | ||||
* @param totalOfName - How many fields with the same name are in the form? | |||||
* @param elementsWithSameName - How many fields with the same name are in the form? | |||||
*/ | */ | ||||
const setInputNumericFieldValue = ( | const setInputNumericFieldValue = ( | ||||
inputEl: HTMLInputNumericElement, | inputEl: HTMLInputNumericElement, | ||||
value: unknown, | value: unknown, | ||||
nthOfName: number, | nthOfName: number, | ||||
totalOfName: number, | |||||
elementsWithSameName: HTMLInputNumericElement[], | |||||
) => { | ) => { | ||||
const valueArray = Array.isArray(value) ? value : [value]; | const valueArray = Array.isArray(value) ? value : [value]; | ||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
inputEl.valueAsNumber = Number(valueArray[totalOfName > 1 ? nthOfName : 0]); | |||||
inputEl.valueAsNumber = Number(valueArray[elementsWithSameName.length > 1 ? nthOfName : 0]); | |||||
}; | }; | ||||
/** | /** | ||||
@@ -472,7 +528,11 @@ const getInputDateLikeFieldValue = ( | |||||
options = {} as GetInputDateFieldValueOptions, | options = {} as GetInputDateFieldValueOptions, | ||||
) => { | ) => { | ||||
if (options.forceDateValues) { | if (options.forceDateValues) { | ||||
return inputEl.valueAsDate; | |||||
return ( | |||||
// somehow datetime-local does not return us the current `valueAsDate` when the string | |||||
// representation in `value` is incomplete. | |||||
inputEl.type === INPUT_TYPE_DATETIME_LOCAL ? new Date(inputEl.value) : inputEl.valueAsDate | |||||
); | |||||
} | } | ||||
return inputEl.value; | return inputEl.value; | ||||
}; | }; | ||||
@@ -492,20 +552,22 @@ const DATE_FORMAT_ISO_MONTH = 'yyyy-MM' as const; | |||||
* @param inputEl - The element. | * @param inputEl - The element. | ||||
* @param value - Value of the input element. | * @param value - Value of the input element. | ||||
* @param nthOfName - What order is this field in with respect to fields of the same name? | * @param nthOfName - What order is this field in with respect to fields of the same name? | ||||
* @param totalOfName - How many fields with the same name are in the form? | |||||
* @param elementsOfSameName - How many fields with the same name are in the form? | |||||
*/ | */ | ||||
const setInputDateLikeFieldValue = ( | const setInputDateLikeFieldValue = ( | ||||
inputEl: HTMLInputDateLikeElement, | inputEl: HTMLInputDateLikeElement, | ||||
value: unknown, | value: unknown, | ||||
nthOfName: number, | nthOfName: number, | ||||
totalOfName: number, | |||||
elementsOfSameName: HTMLInputDateLikeElement[], | |||||
) => { | ) => { | ||||
const valueArray = Array.isArray(value) ? value : [value]; | const valueArray = Array.isArray(value) ? value : [value]; | ||||
const hasMultipleElementsOfSameName = elementsOfSameName.length > 1; | |||||
const elementIndex = hasMultipleElementsOfSameName ? nthOfName : 0; | |||||
if (inputEl.type.toLowerCase() === INPUT_TYPE_DATE) { | if (inputEl.type.toLowerCase() === INPUT_TYPE_DATE) { | ||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
inputEl.value = new Date( | inputEl.value = new Date( | ||||
valueArray[totalOfName > 1 ? nthOfName : 0] as ConstructorParameters<typeof Date>[0], | |||||
valueArray[elementIndex] as ConstructorParameters<typeof Date>[0], | |||||
) | ) | ||||
.toISOString() | .toISOString() | ||||
.slice(0, DATE_FORMAT_ISO_DATE.length); | .slice(0, DATE_FORMAT_ISO_DATE.length); | ||||
@@ -515,7 +577,7 @@ const setInputDateLikeFieldValue = ( | |||||
if (inputEl.type.toLowerCase() === INPUT_TYPE_DATETIME_LOCAL) { | if (inputEl.type.toLowerCase() === INPUT_TYPE_DATETIME_LOCAL) { | ||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
inputEl.value = new Date( | inputEl.value = new Date( | ||||
valueArray[totalOfName > 1 ? nthOfName : 0] as ConstructorParameters<typeof Date>[0], | |||||
valueArray[elementIndex] as ConstructorParameters<typeof Date>[0], | |||||
) | ) | ||||
.toISOString() | .toISOString() | ||||
.slice(0, -1); // remove extra 'Z' suffix | .slice(0, -1); // remove extra 'Z' suffix | ||||
@@ -524,7 +586,7 @@ const setInputDateLikeFieldValue = ( | |||||
if (inputEl.type.toLowerCase() === INPUT_TYPE_MONTH) { | if (inputEl.type.toLowerCase() === INPUT_TYPE_MONTH) { | ||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
inputEl.value = new Date( | inputEl.value = new Date( | ||||
valueArray[totalOfName > 1 ? nthOfName : 0] as ConstructorParameters<typeof Date>[0], | |||||
valueArray[elementIndex] as ConstructorParameters<typeof Date>[0], | |||||
) | ) | ||||
.toISOString() | .toISOString() | ||||
.slice(0, DATE_FORMAT_ISO_MONTH.length); // remove extra 'Z' suffix | .slice(0, DATE_FORMAT_ISO_MONTH.length); // remove extra 'Z' suffix | ||||
@@ -580,6 +642,11 @@ const INPUT_TYPE_HIDDEN = 'hidden' as const; | |||||
*/ | */ | ||||
const INPUT_TYPE_COLOR = 'color' as const; | const INPUT_TYPE_COLOR = 'color' as const; | ||||
/** | |||||
* Value of the `type` attribute for `<input>` elements considered as time pickers. | |||||
*/ | |||||
const INPUT_TYPE_TIME = 'time' as const; | |||||
/** | /** | ||||
* Gets the value of an `<input>` element. | * Gets the value of an `<input>` element. | ||||
* @param inputEl - The element. | * @param inputEl - The element. | ||||
@@ -612,12 +679,13 @@ const getInputFieldValue = ( | |||||
case INPUT_TYPE_PASSWORD: | case INPUT_TYPE_PASSWORD: | ||||
case INPUT_TYPE_HIDDEN: | case INPUT_TYPE_HIDDEN: | ||||
case INPUT_TYPE_COLOR: | case INPUT_TYPE_COLOR: | ||||
case INPUT_TYPE_TIME: | |||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
// force return `null` for custom elements supporting setting values. | |||||
return inputEl.value ?? null; | |||||
// don't force returning `null` for custom elements supporting setting values. | |||||
return inputEl.value; | |||||
}; | }; | ||||
/** | /** | ||||
@@ -625,7 +693,7 @@ const getInputFieldValue = ( | |||||
* @param inputEl - The element. | * @param inputEl - The element. | ||||
* @param value - Value of the input element. | * @param value - Value of the input element. | ||||
* @param nthOfName - What order is this field in with respect to fields of the same name? | * @param nthOfName - What order is this field in with respect to fields of the same name? | ||||
* @param totalOfName - How many fields with the same name are in the form? | |||||
* @param elementsWithSameName - How many fields with the same name are in the form? | |||||
* @note This function is a noop for `<input type="file">` because by design, file inputs are not | * @note This function is a noop for `<input type="file">` because by design, file inputs are not | ||||
* assignable programmatically. | * assignable programmatically. | ||||
*/ | */ | ||||
@@ -633,7 +701,7 @@ const setInputFieldValue = ( | |||||
inputEl: HTMLInputElement, | inputEl: HTMLInputElement, | ||||
value: unknown, | value: unknown, | ||||
nthOfName: number, | nthOfName: number, | ||||
totalOfName: number, | |||||
elementsWithSameName: HTMLInputElement[], | |||||
) => { | ) => { | ||||
switch (inputEl.type.toLowerCase()) { | switch (inputEl.type.toLowerCase()) { | ||||
case INPUT_TYPE_CHECKBOX: | case INPUT_TYPE_CHECKBOX: | ||||
@@ -651,7 +719,7 @@ const setInputFieldValue = ( | |||||
inputEl as HTMLInputNumericElement, | inputEl as HTMLInputNumericElement, | ||||
value, | value, | ||||
nthOfName, | nthOfName, | ||||
totalOfName, | |||||
elementsWithSameName as HTMLInputNumericElement[], | |||||
); | ); | ||||
return; | return; | ||||
case INPUT_TYPE_DATE: | case INPUT_TYPE_DATE: | ||||
@@ -661,7 +729,7 @@ const setInputFieldValue = ( | |||||
inputEl as HTMLInputDateLikeElement, | inputEl as HTMLInputDateLikeElement, | ||||
value, | value, | ||||
nthOfName, | nthOfName, | ||||
totalOfName, | |||||
elementsWithSameName as HTMLInputDateLikeElement[], | |||||
); | ); | ||||
return; | return; | ||||
case INPUT_TYPE_TEXT: | case INPUT_TYPE_TEXT: | ||||
@@ -672,11 +740,12 @@ const setInputFieldValue = ( | |||||
case INPUT_TYPE_PASSWORD: | case INPUT_TYPE_PASSWORD: | ||||
case INPUT_TYPE_HIDDEN: | case INPUT_TYPE_HIDDEN: | ||||
case INPUT_TYPE_COLOR: | case INPUT_TYPE_COLOR: | ||||
case INPUT_TYPE_TIME: | |||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
if (Array.isArray(value) && totalOfName > 1) { | |||||
if (Array.isArray(value) && elementsWithSameName.length > 1) { | |||||
// eslint-disable-next-line no-param-reassign | // eslint-disable-next-line no-param-reassign | ||||
inputEl.value = value[nthOfName]; | inputEl.value = value[nthOfName]; | ||||
return; | return; | ||||
@@ -700,23 +769,24 @@ type HTMLElementWithName | |||||
= (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLButtonElement); | = (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLButtonElement); | ||||
/** | /** | ||||
* Gets the value of a field element. | |||||
* Gets the value of an element regardless if it's a field element or not. | |||||
* @param el - The field element. | * @param el - The field element. | ||||
* @param options - The options. | * @param options - The options. | ||||
* @returns Value of the field element. | |||||
* @returns Value of the element. | |||||
*/ | */ | ||||
export const getFieldValue = (el: HTMLElement, options = {} as GetFieldValueOptions) => { | |||||
export const getValue = (el: HTMLElement, options = {} as GetFieldValueOptions) => { | |||||
switch (el.tagName) { | switch (el.tagName) { | ||||
case TAG_NAME_TEXTAREA: | case TAG_NAME_TEXTAREA: | ||||
return getTextAreaFieldValue(el as HTMLTextAreaElement, options); | return getTextAreaFieldValue(el as HTMLTextAreaElement, options); | ||||
case TAG_NAME_SELECT: | case TAG_NAME_SELECT: | ||||
return getSelectFieldValue(el as HTMLSelectElement); | return getSelectFieldValue(el as HTMLSelectElement); | ||||
case TAG_NAME_INPUT: | case TAG_NAME_INPUT: | ||||
return getInputFieldValue(el as HTMLInputElement, options); | |||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
return getInputFieldValue(el as HTMLInputElement, options); | |||||
return 'value' in el ? el.value : null; | |||||
}; | }; | ||||
/** | /** | ||||
@@ -724,27 +794,42 @@ export const getFieldValue = (el: HTMLElement, options = {} as GetFieldValueOpti | |||||
* @param el - The field element. | * @param el - The field element. | ||||
* @param value - Value of the field element. | * @param value - Value of the field element. | ||||
* @param nthOfName - What order is this field in with respect to fields of the same name? | * @param nthOfName - What order is this field in with respect to fields of the same name? | ||||
* @param totalOfName - How many fields with the same name are in the form? | |||||
* @param elementsWithSameName - How many fields with the same name are in the form? | |||||
*/ | */ | ||||
const setFieldValue = ( | const setFieldValue = ( | ||||
el: HTMLElement, | el: HTMLElement, | ||||
value: unknown, | value: unknown, | ||||
nthOfName: number, | nthOfName: number, | ||||
totalOfName: number, | |||||
elementsWithSameName: HTMLElement[], | |||||
) => { | ) => { | ||||
switch (el.tagName) { | switch (el.tagName) { | ||||
case TAG_NAME_TEXTAREA: | case TAG_NAME_TEXTAREA: | ||||
setTextAreaFieldValue(el as HTMLTextAreaElement, value, nthOfName, totalOfName); | |||||
setTextAreaFieldValue( | |||||
el as HTMLTextAreaElement, | |||||
value, | |||||
nthOfName, | |||||
elementsWithSameName as HTMLTextAreaElement[], | |||||
); | |||||
return; | return; | ||||
case TAG_NAME_SELECT: | case TAG_NAME_SELECT: | ||||
setSelectFieldValue(el as HTMLSelectElement, value); | |||||
setSelectFieldValue( | |||||
el as HTMLSelectElement, | |||||
value, | |||||
nthOfName, | |||||
elementsWithSameName as HTMLSelectElement[], | |||||
); | |||||
return; | return; | ||||
case TAG_NAME_INPUT: | case TAG_NAME_INPUT: | ||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
setInputFieldValue(el as HTMLInputElement, value, nthOfName, totalOfName); | |||||
setInputFieldValue( | |||||
el as HTMLInputElement, | |||||
value, | |||||
nthOfName, | |||||
elementsWithSameName as HTMLInputElement[], | |||||
); | |||||
}; | }; | ||||
/** | /** | ||||
@@ -758,17 +843,17 @@ const ATTRIBUTE_NAME = 'name' as const; | |||||
const ATTRIBUTE_DISABLED = 'disabled' as const; | const ATTRIBUTE_DISABLED = 'disabled' as const; | ||||
/** | /** | ||||
* Determines if an element is a named and enabled form field. | |||||
* Determines if an element's value is included when its form is submitted. | |||||
* @param el - The element. | * @param el - The element. | ||||
* @returns Value determining if the element is a named and enabled form field. | |||||
* @returns Value determining if the element's value is included when its form is submitted. | |||||
*/ | */ | ||||
export const isNamedEnabledFormFieldElement = (el: HTMLElement) => { | |||||
export const isElementValueIncludedInFormSubmit = (el: HTMLElement) => { | |||||
const namedEl = el as unknown as Record<string, unknown>; | const namedEl = el as unknown as Record<string, unknown>; | ||||
return ( | return ( | ||||
typeof namedEl[ATTRIBUTE_NAME] === 'string' | typeof namedEl[ATTRIBUTE_NAME] === 'string' | ||||
&& namedEl[ATTRIBUTE_NAME].length > 0 | && namedEl[ATTRIBUTE_NAME].length > 0 | ||||
&& !(ATTRIBUTE_DISABLED in namedEl && Boolean(namedEl[ATTRIBUTE_DISABLED])) | && !(ATTRIBUTE_DISABLED in namedEl && Boolean(namedEl[ATTRIBUTE_DISABLED])) | ||||
&& isFormFieldElement(namedEl as unknown as HTMLElement) | |||||
&& isFieldElement(namedEl as unknown as HTMLElement) | |||||
); | ); | ||||
}; | }; | ||||
@@ -829,7 +914,7 @@ const filterFieldElements = (form: HTMLFormElement) => { | |||||
!Number.isNaN(Number(k)) | !Number.isNaN(Number(k)) | ||||
// Only the enabled/read-only elements can be enumerated. | // Only the enabled/read-only elements can be enumerated. | ||||
&& isNamedEnabledFormFieldElement(el) | |||||
&& isElementValueIncludedInFormSubmit(el) | |||||
)) as [string, HTMLElementWithName][]; | )) as [string, HTMLElementWithName][]; | ||||
}; | }; | ||||
@@ -845,7 +930,7 @@ export const getFormValues = (form: HTMLFormElement, options = {} as GetFormValu | |||||
const fieldElements = filterFieldElements(form); | const fieldElements = filterFieldElements(form); | ||||
const fieldValues = fieldElements.reduce( | const fieldValues = fieldElements.reduce( | ||||
(theFormValues, [, el]) => { | (theFormValues, [, el]) => { | ||||
const fieldValue = getFieldValue(el, options); | |||||
const fieldValue = getValue(el, options); | |||||
if (fieldValue === null) { | if (fieldValue === null) { | ||||
return theFormValues; | return theFormValues; | ||||
} | } | ||||
@@ -853,24 +938,35 @@ export const getFormValues = (form: HTMLFormElement, options = {} as GetFormValu | |||||
const { name: fieldName } = el; | const { name: fieldName } = el; | ||||
const { [fieldName]: oldFormValue = null } = theFormValues; | const { [fieldName]: oldFormValue = null } = theFormValues; | ||||
if (oldFormValue === null) { | |||||
if (oldFormValue !== null && !Array.isArray(oldFormValue)) { | |||||
return { | return { | ||||
...theFormValues, | ...theFormValues, | ||||
[fieldName]: fieldValue, | |||||
[fieldName]: [oldFormValue, fieldValue], | |||||
}; | }; | ||||
} | } | ||||
if (!Array.isArray(oldFormValue)) { | |||||
if (Array.isArray(oldFormValue)) { | |||||
if (Array.isArray(fieldValue)) { | |||||
return { | |||||
...theFormValues, | |||||
[fieldName]: [...oldFormValue, ...fieldValue], | |||||
}; | |||||
} | |||||
return { | return { | ||||
...theFormValues, | ...theFormValues, | ||||
[fieldName]: [oldFormValue, fieldValue], | |||||
[fieldName]: [...oldFormValue, fieldValue], | |||||
}; | }; | ||||
} | } | ||||
return { | return { | ||||
...theFormValues, | ...theFormValues, | ||||
[fieldName]: [...oldFormValue, fieldValue], | |||||
[fieldName]: fieldValue, | |||||
}; | }; | ||||
// return { | |||||
// ...theFormValues, | |||||
// [fieldName]: [...oldFormValue, fieldValue], | |||||
// }; | |||||
}, | }, | ||||
{} as Record<string, unknown>, | {} as Record<string, unknown>, | ||||
); | ); | ||||
@@ -928,36 +1024,46 @@ export const setFormValues = ( | |||||
const fieldElements = filterFieldElements(form); | const fieldElements = filterFieldElements(form); | ||||
const objectValues = normalizeValues(values); | const objectValues = normalizeValues(values); | ||||
const count = fieldElements | |||||
const elementsWithSameName = fieldElements | |||||
.filter(([, el]) => el.name in objectValues) | .filter(([, el]) => el.name in objectValues) | ||||
.reduce( | .reduce( | ||||
(currentCount, [, el]) => { | (currentCount, [, el]) => { | ||||
if (el.tagName === TAG_NAME_INPUT && el.type === INPUT_TYPE_RADIO) { | if (el.tagName === TAG_NAME_INPUT && el.type === INPUT_TYPE_RADIO) { | ||||
return { | return { | ||||
...currentCount, | ...currentCount, | ||||
[el.name]: 1, | |||||
[el.name]: [el], | |||||
}; | }; | ||||
} | } | ||||
return { | return { | ||||
...currentCount, | ...currentCount, | ||||
[el.name]: ( | [el.name]: ( | ||||
typeof currentCount[el.name] === 'number' | |||||
? currentCount[el.name] + 1 | |||||
: 1 | |||||
Array.isArray(currentCount[el.name]) | |||||
? [...currentCount[el.name], el] | |||||
: [el] | |||||
), | ), | ||||
}; | }; | ||||
}, | }, | ||||
{} as Record<string, number>, | |||||
{} as Record<string, HTMLElement[]>, | |||||
); | ); | ||||
const counter = {} as Record<string, number>; | |||||
const nthElementOfName = {} as Record<string, number>; | |||||
fieldElements | fieldElements | ||||
.filter(([, el]) => el.name in objectValues) | .filter(([, el]) => el.name in objectValues) | ||||
.forEach(([, el]) => { | .forEach(([, el]) => { | ||||
counter[el.name] = typeof counter[el.name] === 'number' ? counter[el.name] + 1 : 0; | |||||
setFieldValue(el, objectValues[el.name], counter[el.name], count[el.name]); | |||||
nthElementOfName[el.name] = ( | |||||
typeof nthElementOfName[el.name] === 'number' | |||||
? nthElementOfName[el.name] + 1 | |||||
: 0 | |||||
); | |||||
setFieldValue( | |||||
el, | |||||
objectValues[el.name], | |||||
nthElementOfName[el.name], | |||||
elementsWithSameName[el.name], | |||||
); | |||||
}); | }); | ||||
}; | }; | ||||