diff --git a/cypress.json b/cypress.json
index 60ed5aa..8513da0 100644
--- a/cypress.json
+++ b/cypress.json
@@ -1,3 +1,4 @@
{
- "video": false
+ "video": false,
+ "screenshotOnRunFailure": false
}
diff --git a/cypress/integration/checkbox.test.ts b/cypress/integration/checkbox.test.ts
index d1fc7a4..62042e8 100644
--- a/cypress/integration/checkbox.test.ts
+++ b/cypress/integration/checkbox.test.ts
@@ -1,4 +1,4 @@
-import { getFormValues } from '../../src'
+import { getFormValues, setFormValues } from '../../src';
import * as utils from '../utils'
describe('checkbox', () => {
@@ -91,5 +91,78 @@ describe('checkbox', () => {
expectedStaticValue: 'enabled=on',
});
});
- })
-})
+ });
+
+
+ describe('duplicate', () => {
+ beforeEach(utils.setup(`
+
+
+
+
+ Text/Basic
+
+
+
+
+
+ `));
+
+ 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: {
+ enabled: ['hello 1', 'hello 2'],
+ },
+ });
+ });
+
+ it('should set both values', () => {
+ utils.test({
+ preAction: (form: HTMLFormElement) => {
+ setFormValues(form, {
+ enabled: ['hello 3', 'hello 4'],
+ })
+ },
+ 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: {
+ enabled: ['hello 3', 'hello 4'],
+ },
+ });
+ });
+ });
+});
diff --git a/cypress/integration/text.test.ts b/cypress/integration/text.test.ts
index 84e5df4..df1a626 100644
--- a/cypress/integration/text.test.ts
+++ b/cypress/integration/text.test.ts
@@ -1,4 +1,4 @@
-import { getFormValues, setFormValues } from '../../src';
+import { getFormValues, LineEnding, setFormValues } from '../../src';
import * as utils from '../utils'
describe('text', () => {
@@ -158,7 +158,7 @@ describe('text', () => {
});
});
- describe('programmatical value setting', () => {
+ describe('programmatic value setting', () => {
beforeEach(utils.setup(`
@@ -199,5 +199,151 @@ describe('text', () => {
});
});
- // TODO implement textarea tests
+ describe('textarea', () => {
+ beforeEach(utils.setup(`
+
+
+
+
+ Text/Basic
+
+
+
+
+
+ `));
+
+ it('should read LF line breaks', () => {
+ utils.test({
+ action: (cy: any) => {
+ cy.get('[name="hello"]')
+ .type('Hi\nHello', { parseSpecialCharSequences: false })
+ return cy.get('[type="submit"]')
+ },
+ test: (form: HTMLFormElement, submitter: any, search: any) => {
+ const before = utils.makeSearchParams(getFormValues(form, { submitter, lineEndings: LineEnding.LF }))
+ .toString();
+ const after = utils.makeSearchParams(search)
+ .toString();
+ expect(before)
+ .toEqual(after);
+ },
+ expectedStaticValue: {
+ hello: 'Hi\nHello',
+ },
+ });
+ });
+
+ it('should read CR line breaks', () => {
+ utils.test({
+ action: (cy: any) => {
+ cy.get('[name="hello"]')
+ .type('Hi\rHello', { parseSpecialCharSequences: false })
+ return cy.get('[type="submit"]')
+ },
+ test: (form: HTMLFormElement, submitter: any, search: any) => {
+ const before = utils.makeSearchParams(getFormValues(form, { submitter, lineEndings: LineEnding.CR }))
+ .toString();
+ const after = utils.makeSearchParams(search)
+ .toString();
+ expect(before)
+ .toEqual(after);
+ },
+ expectedStaticValue: {
+ hello: 'Hi\rHello',
+ },
+ });
+ });
+
+ it('should read CRLF line breaks', () => {
+ utils.test({
+ action: (cy: any) => {
+ cy.get('[name="hello"]')
+ .type('Hi\r\nHello', { parseSpecialCharSequences: false })
+ return cy.get('[type="submit"]')
+ },
+ test: (form: HTMLFormElement, submitter: any, search: any) => {
+ const before = utils.makeSearchParams(getFormValues(form, { submitter, lineEndings: LineEnding.CRLF }))
+ .toString();
+ const after = utils.makeSearchParams(search)
+ .toString();
+ expect(before)
+ .toEqual(after);
+ },
+ expectedStaticValue: {
+ hello: 'Hi\r\nHello',
+ },
+ });
+ });
+ });
+
+ describe('duplicate', () => {
+ beforeEach(utils.setup(`
+
+
+
+
+ Text/Basic
+
+
+
+
+
+ `));
+
+ 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: ['value', 'another value'],
+ },
+ });
+ });
+
+ it('should set both values', () => {
+ utils.test({
+ preAction: (form: HTMLFormElement) => {
+ setFormValues(form, {
+ hello: ['new value 1', 'another value 2'],
+ })
+ },
+ 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: ['new value 1', 'another value 2'],
+ },
+ });
+ });
+ });
})
diff --git a/cypress/utils/index.ts b/cypress/utils/index.ts
index 04fcf6a..654437a 100644
--- a/cypress/utils/index.ts
+++ b/cypress/utils/index.ts
@@ -2,7 +2,7 @@
import JSDOMDummyCypress from './jsdom-compat'
-type ExpectedSearchValue = Record | string
+type ExpectedSearchValue = any
type RetrieveSubmitterFn = (wrapper: typeof cy | JSDOMDummyCypress) => any
@@ -105,7 +105,21 @@ export const makeSearchParams = (beforeValues: Record | string)
(beforeSearchParams, [key, value]) => {
const theValue = !Array.isArray(value) ? [value] : value
theValue.forEach(v => {
- beforeSearchParams.append(key, v)
+ let processedLineBreaks = v
+ if (typeof cy !== 'undefined') {
+ let forceLineBreaks: string;
+
+ // TODO make this foolproof
+ if (navigator.platform.indexOf("Mac") === 0 ||
+ navigator.platform === "iPhone") {
+ forceLineBreaks = '\n';
+ } else if (navigator.platform === 'Win32') {
+ forceLineBreaks = '\r\n';
+ }
+ processedLineBreaks = processedLineBreaks
+ .replace(/(\r\n|\r|\n)/g, forceLineBreaks)
+ }
+ beforeSearchParams.append(key, processedLineBreaks)
})
return beforeSearchParams
},
diff --git a/cypress/utils/jsdom-compat.ts b/cypress/utils/jsdom-compat.ts
index cee039b..fdf225d 100644
--- a/cypress/utils/jsdom-compat.ts
+++ b/cypress/utils/jsdom-compat.ts
@@ -9,7 +9,8 @@ class JSDOMJQuery {
this.selectedElements = Array.from(elements)
}
- type(s: string) {
+ type(sRaw: string) {
+ const s = sRaw.replace(/\{enter}/g, '\n');
this.selectedElements.forEach((el: any) => {
if (el.tagName === 'TEXTAREA') {
el.innerText = s
diff --git a/src/index.ts b/src/index.ts
index 5b5e507..5926deb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,7 +3,7 @@
*/
export enum LineEnding {
/**
- * Carriage return. Used for legacy Mac OS systems.
+ * Carriage return. Used for legacy macOS systems.
*/
CR = '\r',
/**
@@ -274,10 +274,15 @@ const setInputCheckboxFieldValue = (
inputEl: HTMLInputCheckboxElement,
value: unknown,
) => {
- const checkedValue = inputEl.getAttribute(ATTRIBUTE_VALUE);
- if (checkedValue !== null) {
+ const valueWhenChecked = inputEl.getAttribute(ATTRIBUTE_VALUE);
+
+ if (valueWhenChecked !== null) {
// eslint-disable-next-line no-param-reassign
- inputEl.checked = value === checkedValue;
+ inputEl.checked = (
+ Array.isArray(value)
+ ? value.includes(valueWhenChecked)
+ : value === valueWhenChecked
+ );
return;
}
@@ -542,12 +547,16 @@ const getInputFieldValue = (
* Sets the value of an `` element.
* @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?
* @note This function is a noop for `` because by design, file inputs are not
* assignable programmatically.
*/
const setInputFieldValue = (
inputEl: HTMLInputElement,
value: unknown,
+ nthOfName: number,
+ totalOfName: number,
) => {
switch (inputEl.type.toLowerCase()) {
case INPUT_TYPE_CHECKBOX:
@@ -570,6 +579,13 @@ const setInputFieldValue = (
default:
break;
}
+
+ if (Array.isArray(value) && totalOfName > 1) {
+ // eslint-disable-next-line no-param-reassign
+ inputEl.value = value[nthOfName];
+ return;
+ }
+
// eslint-disable-next-line no-param-reassign
inputEl.value = value as string;
};
@@ -614,8 +630,15 @@ export const getFieldValue = (el: HTMLElement, options = {} as GetFieldValueOpti
* Sets the value of a field element.
* @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?
*/
-const setFieldValue = (el: HTMLElement, value: unknown) => {
+const setFieldValue = (
+ el: HTMLElement,
+ value: unknown,
+ nthOfName: number,
+ totalOfName: number,
+) => {
switch (el.tagName) {
case TAG_NAME_TEXTAREA:
setTextAreaFieldValue(el as HTMLTextAreaElement, value);
@@ -624,7 +647,7 @@ const setFieldValue = (el: HTMLElement, value: unknown) => {
setSelectFieldValue(el as HTMLSelectElement, value);
return;
case TAG_NAME_INPUT:
- setInputFieldValue(el as HTMLInputElement, value);
+ setInputFieldValue(el as HTMLInputElement, value, nthOfName, totalOfName);
return;
default:
break;
@@ -780,6 +803,22 @@ export const getFormValues = (form: HTMLFormElement, options = {} as GetFormValu
return fieldValues;
};
+const normalizeValues = (values: unknown): Record => {
+ if (typeof values === 'string') {
+ return Object.fromEntries(new URLSearchParams(values).entries());
+ }
+
+ if (values instanceof URLSearchParams) {
+ return Object.fromEntries(values.entries());
+ }
+
+ if (Array.isArray(values)) {
+ return Object.fromEntries(values);
+ }
+
+ return values as Record;
+};
+
/**
* Sets the values of all the fields within the form through accessing the DOM nodes. Partial values
* may be passed to set values only to certain form fields.
@@ -802,11 +841,33 @@ export const setFormValues = (
}
const fieldElements = filterFieldElements(form);
- const objectValues = new URLSearchParams(values as unknown as string | Record);
+ const objectValues = normalizeValues(values);
+
+ const count = fieldElements
+ .filter(([, el]) => el.name in objectValues)
+ .reduce(
+ (currentCount, [, el]) => ({
+ ...currentCount,
+ [el.name]: (
+ el.tagName === TAG_NAME_INPUT && el.type === INPUT_TYPE_RADIO
+ ? 1
+ : (
+ typeof currentCount[el.name] === 'number'
+ ? currentCount[el.name] + 1
+ : 1
+ )
+ ),
+ }),
+ {} as Record,
+ );
+
+ const counter = {} as Record;
+
fieldElements
- .filter(([, el]) => objectValues.has(el.name))
+ .filter(([, el]) => el.name in objectValues)
.forEach(([, el]) => {
- setFieldValue(el, objectValues.get(el.name));
+ counter[el.name] = typeof counter[el.name] === 'number' ? counter[el.name] + 1 : 0;
+ setFieldValue(el, objectValues[el.name], counter[el.name], count[el.name]);
});
};