瀏覽代碼

Make coverage 100%

Include all tests for the primary functions as well as the utilities.
master
TheoryOfNekomata 1 年之前
父節點
當前提交
5fa727bf45
共有 8 個檔案被更改,包括 1109 行新增86 行删除
  1. +218
    -2
      cypress/integration/checkbox.test.ts
  2. +13
    -0
      cypress/integration/date.test.ts
  3. +13
    -0
      cypress/integration/datetime-local.test.ts
  4. +214
    -2
      cypress/integration/misc.test.ts
  5. +13
    -0
      cypress/integration/month.test.ts
  6. +186
    -1
      cypress/integration/select.test.ts
  7. +265
    -0
      cypress/integration/time.test.ts
  8. +187
    -81
      src/index.ts

+ 218
- 2
cypress/integration/checkbox.test.ts 查看文件

@@ -55,7 +55,7 @@ describe('checkbox', () => {
}
});
});
})
});

describe('checked', () => {
beforeEach(utils.setup(`
@@ -93,7 +93,6 @@ describe('checkbox', () => {
});
});


describe('duplicate', () => {
beforeEach(utils.setup(`
<!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: '',
});
});
});
});

+ 13
- 0
cypress/integration/date.test.ts 查看文件

@@ -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', () => {


+ 13
- 0
cypress/integration/datetime-local.test.ts 查看文件

@@ -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', () => {


+ 214
- 2
cypress/integration/misc.test.ts 查看文件

@@ -1,4 +1,10 @@
import getFormValuesDeprecated, { getFormValues, setFormValues } from '../../src';
import getFormValuesDeprecated, {
getFormValues,
setFormValues,
isFieldElement,
isElementValueIncludedInFormSubmit,
getValue,
} from '../../src';
import * as utils from '../utils'

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', () => {
beforeEach(utils.setup(`


+ 13
- 0
cypress/integration/month.test.ts 查看文件

@@ -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', () => {


+ 186
- 1
cypress/integration/select.test.ts 查看文件

@@ -1,4 +1,4 @@
import { getFormValues } from '../../src'
import { getFormValues, setFormValues } from '../../src';
import * as utils from '../utils'

describe('select', () => {
@@ -41,6 +41,107 @@ describe('select', () => {
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', () => {
@@ -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'
});
});
})
})

+ 265
- 0
cypress/integration/time.test.ts 查看文件

@@ -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'],
},
});
});
});
})

+ 187
- 81
src/index.ts 查看文件

@@ -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.
* @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;
if (FORM_FIELD_ELEMENT_TAG_NAMES.includes(tagName as typeof FORM_FIELD_ELEMENT_TAG_NAMES[0])) {
return true;
@@ -92,15 +93,15 @@ const getTextAreaFieldValue = (
* @param textareaEl - The 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 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 = (
textareaEl: HTMLTextAreaElement,
value: unknown,
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
textareaEl.value = value[nthOfName];
return;
@@ -128,19 +129,54 @@ const getSelectFieldValue = (
* Sets the value of a `<select>` element.
* @param selectEl - The 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
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;
};

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.
* @param inputEl - The element.
@@ -269,26 +344,10 @@ const setInputCheckboxFieldValue = (
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
inputEl.checked = true;
inputEl.checked = newValue;
}
};

@@ -324,9 +383,6 @@ const getInputFileFieldValue = (
options = {} as GetInputFileFieldValueOptions,
) => {
const { files } = inputEl;
if ((files as unknown) === null) {
return null;
}
if (options.getFileObjects) {
return files;
}
@@ -395,17 +451,17 @@ const getInputNumericFieldValue = (
* @param inputEl - The 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 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 = (
inputEl: HTMLInputNumericElement,
value: unknown,
nthOfName: number,
totalOfName: number,
elementsWithSameName: HTMLInputNumericElement[],
) => {
const valueArray = Array.isArray(value) ? value : [value];
// 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,
) => {
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;
};
@@ -492,20 +552,22 @@ const DATE_FORMAT_ISO_MONTH = 'yyyy-MM' as const;
* @param inputEl - The 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 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 = (
inputEl: HTMLInputDateLikeElement,
value: unknown,
nthOfName: number,
totalOfName: number,
elementsOfSameName: HTMLInputDateLikeElement[],
) => {
const valueArray = Array.isArray(value) ? value : [value];
const hasMultipleElementsOfSameName = elementsOfSameName.length > 1;
const elementIndex = hasMultipleElementsOfSameName ? nthOfName : 0;

if (inputEl.type.toLowerCase() === INPUT_TYPE_DATE) {
// eslint-disable-next-line no-param-reassign
inputEl.value = new Date(
valueArray[totalOfName > 1 ? nthOfName : 0] as ConstructorParameters<typeof Date>[0],
valueArray[elementIndex] as ConstructorParameters<typeof Date>[0],
)
.toISOString()
.slice(0, DATE_FORMAT_ISO_DATE.length);
@@ -515,7 +577,7 @@ const setInputDateLikeFieldValue = (
if (inputEl.type.toLowerCase() === INPUT_TYPE_DATETIME_LOCAL) {
// eslint-disable-next-line no-param-reassign
inputEl.value = new Date(
valueArray[totalOfName > 1 ? nthOfName : 0] as ConstructorParameters<typeof Date>[0],
valueArray[elementIndex] as ConstructorParameters<typeof Date>[0],
)
.toISOString()
.slice(0, -1); // remove extra 'Z' suffix
@@ -524,7 +586,7 @@ const setInputDateLikeFieldValue = (
if (inputEl.type.toLowerCase() === INPUT_TYPE_MONTH) {
// eslint-disable-next-line no-param-reassign
inputEl.value = new Date(
valueArray[totalOfName > 1 ? nthOfName : 0] as ConstructorParameters<typeof Date>[0],
valueArray[elementIndex] as ConstructorParameters<typeof Date>[0],
)
.toISOString()
.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;

/**
* 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.
* @param inputEl - The element.
@@ -612,12 +679,13 @@ const getInputFieldValue = (
case INPUT_TYPE_PASSWORD:
case INPUT_TYPE_HIDDEN:
case INPUT_TYPE_COLOR:
case INPUT_TYPE_TIME:
default:
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 value - Value of the input element.
* @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
* assignable programmatically.
*/
@@ -633,7 +701,7 @@ const setInputFieldValue = (
inputEl: HTMLInputElement,
value: unknown,
nthOfName: number,
totalOfName: number,
elementsWithSameName: HTMLInputElement[],
) => {
switch (inputEl.type.toLowerCase()) {
case INPUT_TYPE_CHECKBOX:
@@ -651,7 +719,7 @@ const setInputFieldValue = (
inputEl as HTMLInputNumericElement,
value,
nthOfName,
totalOfName,
elementsWithSameName as HTMLInputNumericElement[],
);
return;
case INPUT_TYPE_DATE:
@@ -661,7 +729,7 @@ const setInputFieldValue = (
inputEl as HTMLInputDateLikeElement,
value,
nthOfName,
totalOfName,
elementsWithSameName as HTMLInputDateLikeElement[],
);
return;
case INPUT_TYPE_TEXT:
@@ -672,11 +740,12 @@ const setInputFieldValue = (
case INPUT_TYPE_PASSWORD:
case INPUT_TYPE_HIDDEN:
case INPUT_TYPE_COLOR:
case INPUT_TYPE_TIME:
default:
break;
}

if (Array.isArray(value) && totalOfName > 1) {
if (Array.isArray(value) && elementsWithSameName.length > 1) {
// eslint-disable-next-line no-param-reassign
inputEl.value = value[nthOfName];
return;
@@ -700,23 +769,24 @@ type HTMLElementWithName
= (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 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) {
case TAG_NAME_TEXTAREA:
return getTextAreaFieldValue(el as HTMLTextAreaElement, options);
case TAG_NAME_SELECT:
return getSelectFieldValue(el as HTMLSelectElement);
case TAG_NAME_INPUT:
return getInputFieldValue(el as HTMLInputElement, options);
default:
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 value - Value of the field element.
* @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 = (
el: HTMLElement,
value: unknown,
nthOfName: number,
totalOfName: number,
elementsWithSameName: HTMLElement[],
) => {
switch (el.tagName) {
case TAG_NAME_TEXTAREA:
setTextAreaFieldValue(el as HTMLTextAreaElement, value, nthOfName, totalOfName);
setTextAreaFieldValue(
el as HTMLTextAreaElement,
value,
nthOfName,
elementsWithSameName as HTMLTextAreaElement[],
);
return;
case TAG_NAME_SELECT:
setSelectFieldValue(el as HTMLSelectElement, value);
setSelectFieldValue(
el as HTMLSelectElement,
value,
nthOfName,
elementsWithSameName as HTMLSelectElement[],
);
return;
case TAG_NAME_INPUT:
default:
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;

/**
* 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.
* @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>;
return (
typeof namedEl[ATTRIBUTE_NAME] === 'string'
&& namedEl[ATTRIBUTE_NAME].length > 0
&& !(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))

// Only the enabled/read-only elements can be enumerated.
&& isNamedEnabledFormFieldElement(el)
&& isElementValueIncludedInFormSubmit(el)
)) as [string, HTMLElementWithName][];
};

@@ -845,7 +930,7 @@ export const getFormValues = (form: HTMLFormElement, options = {} as GetFormValu
const fieldElements = filterFieldElements(form);
const fieldValues = fieldElements.reduce(
(theFormValues, [, el]) => {
const fieldValue = getFieldValue(el, options);
const fieldValue = getValue(el, options);
if (fieldValue === null) {
return theFormValues;
}
@@ -853,24 +938,35 @@ export const getFormValues = (form: HTMLFormElement, options = {} as GetFormValu
const { name: fieldName } = el;
const { [fieldName]: oldFormValue = null } = theFormValues;

if (oldFormValue === null) {
if (oldFormValue !== null && !Array.isArray(oldFormValue)) {
return {
...theFormValues,
[fieldName]: fieldValue,
[fieldName]: [oldFormValue, fieldValue],
};
}

if (!Array.isArray(oldFormValue)) {
if (Array.isArray(oldFormValue)) {
if (Array.isArray(fieldValue)) {
return {
...theFormValues,
[fieldName]: [...oldFormValue, ...fieldValue],
};
}
return {
...theFormValues,
[fieldName]: [oldFormValue, fieldValue],
[fieldName]: [...oldFormValue, fieldValue],
};
}

return {
...theFormValues,
[fieldName]: [...oldFormValue, fieldValue],
[fieldName]: fieldValue,
};

// return {
// ...theFormValues,
// [fieldName]: [...oldFormValue, fieldValue],
// };
},
{} as Record<string, unknown>,
);
@@ -928,36 +1024,46 @@ export const setFormValues = (
const fieldElements = filterFieldElements(form);
const objectValues = normalizeValues(values);

const count = fieldElements
const elementsWithSameName = fieldElements
.filter(([, el]) => el.name in objectValues)
.reduce(
(currentCount, [, el]) => {
if (el.tagName === TAG_NAME_INPUT && el.type === INPUT_TYPE_RADIO) {
return {
...currentCount,
[el.name]: 1,
[el.name]: [el],
};
}

return {
...currentCount,
[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
.filter(([, el]) => el.name in objectValues)
.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],
);
});
};



Loading…
取消
儲存