From 67bd13e7aebaa824484436ad0774e318e9972529 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 16 May 2021 15:36:38 +0800 Subject: [PATCH] Structure tests, document methods The tests do not depend on separate files for loading templates. This is for the layouts to be defined alongside the tests for easier updates. The methods have been refactored and documented for ease of use of the consumers. --- README.md | 12 +- cypress/fixtures/templates/blank.html | 12 - cypress/fixtures/templates/everything.html | 98 ---- .../templates/single-disabled-input.html | 19 - .../fixtures/templates/single-file-input.html | 16 - ...e-input-with-double-button-submitters.html | 17 - ...le-input-with-double-input-submitters.html | 17 - cypress/fixtures/templates/single-input.html | 16 - .../templates/single-multiple-file-input.html | 16 - .../templates/single-multiple-select.html | 21 - .../single-outside-input-and-submitter.html | 15 - .../templates/single-outside-input.html | 18 - .../templates/single-readonly-input.html | 19 - cypress/fixtures/templates/single-select.html | 20 - cypress/fixtures/uploads/data2.json | 3 + cypress/integration/blank.test.ts | 18 - cypress/integration/checkbox.test.ts | 82 ++++ cypress/integration/everything.test.ts | 38 -- cypress/integration/file.test.ts | 142 ++++++ cypress/integration/misc.test.ts | 168 +++++++ cypress/integration/select.test.ts | 81 ++++ cypress/integration/single.test.ts | 200 -------- cypress/integration/submitter.test.ts | 112 +++++ cypress/integration/text.test.ts | 148 ++++++ cypress/utils/index.ts | 95 ++++ cypress/utils/jsdom-compat.ts | 74 +++ package.json | 6 +- src/index.ts | 348 ++++++++++---- test/utils/index.ts | 145 ------ tsconfig.json | 2 +- yarn.lock | 432 +++++++++--------- 31 files changed, 1385 insertions(+), 1025 deletions(-) delete mode 100644 cypress/fixtures/templates/blank.html delete mode 100644 cypress/fixtures/templates/everything.html delete mode 100644 cypress/fixtures/templates/single-disabled-input.html delete mode 100644 cypress/fixtures/templates/single-file-input.html delete mode 100644 cypress/fixtures/templates/single-input-with-double-button-submitters.html delete mode 100644 cypress/fixtures/templates/single-input-with-double-input-submitters.html delete mode 100644 cypress/fixtures/templates/single-input.html delete mode 100644 cypress/fixtures/templates/single-multiple-file-input.html delete mode 100644 cypress/fixtures/templates/single-multiple-select.html delete mode 100644 cypress/fixtures/templates/single-outside-input-and-submitter.html delete mode 100644 cypress/fixtures/templates/single-outside-input.html delete mode 100644 cypress/fixtures/templates/single-readonly-input.html delete mode 100644 cypress/fixtures/templates/single-select.html create mode 100644 cypress/fixtures/uploads/data2.json delete mode 100644 cypress/integration/blank.test.ts create mode 100644 cypress/integration/checkbox.test.ts delete mode 100644 cypress/integration/everything.test.ts create mode 100644 cypress/integration/file.test.ts create mode 100644 cypress/integration/misc.test.ts create mode 100644 cypress/integration/select.test.ts delete mode 100644 cypress/integration/single.test.ts create mode 100644 cypress/integration/submitter.test.ts create mode 100644 cypress/integration/text.test.ts create mode 100644 cypress/utils/index.ts create mode 100644 cypress/utils/jsdom-compat.ts delete mode 100644 test/utils/index.ts diff --git a/README.md b/README.md index 498f296..81c4b09 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# formxtr +# formxtra + +_(read "form extra")_ Extract form values through the DOM. @@ -20,7 +22,7 @@ Upon retrieving the field values somehow, some libraries attempt to duplicate th for instance by attaching event listeners and storing the new values into some internal object or map, which can be retrieved by some other exposed function. -With `formxtr`, there is no need to traverse the DOM for individual fields to get their values. Provided the fields are +With `formxtra`, there is no need to traverse the DOM for individual fields to get their values. Provided the fields are associated to the form (either as a descendant of the `
` element or [associated through the `form=""` attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fae-form)) and has a valid `name`, the values of these fields can be easily extracted, using the `form.elements` attribute built-in to the DOM and @@ -32,8 +34,8 @@ of the field elements are already stored in the DOM, waiting to be accessed. The package can be found on: - [Modal Pack](https://js.pack.modal.sh) -- [npm](https://npmjs.com/package/formxtr) -- [GitHub Package Registry](https://github.com/TheoryOfNekomata/formxtr/packages/784699) +- [npm](https://npmjs.com/package/formxtra) +- [GitHub Package Registry](https://github.com/TheoryOfNekomata/formxtra/packages/784699) ## Usage @@ -60,7 +62,7 @@ For an example form: Use the library as follows (code is in TypeScript, but can work with JavaScript as well): ```typescript -import getFormValues from '@theoryofnekomata/formxtr'; +import getFormValues from '@theoryofnekomata/formxtra'; const form: HTMLFormElement = document.getElementById('form'); diff --git a/cypress/fixtures/templates/blank.html b/cypress/fixtures/templates/blank.html deleted file mode 100644 index c5f859f..0000000 --- a/cypress/fixtures/templates/blank.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Title - - - - -
- - diff --git a/cypress/fixtures/templates/everything.html b/cypress/fixtures/templates/everything.html deleted file mode 100644 index 4174117..0000000 --- a/cypress/fixtures/templates/everything.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Title - - -
-

-
-
- -
-
- -
-
- -
-
- Gender -
- - -
-
-
- -
-
- -
-
- -
-
- - Default Dependents - -
- - -
-
-
- -
-
- -
-
- - -
-
-
- - - diff --git a/cypress/fixtures/templates/single-disabled-input.html b/cypress/fixtures/templates/single-disabled-input.html deleted file mode 100644 index 135df30..0000000 --- a/cypress/fixtures/templates/single-disabled-input.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/templates/single-file-input.html b/cypress/fixtures/templates/single-file-input.html deleted file mode 100644 index 75d862a..0000000 --- a/cypress/fixtures/templates/single-file-input.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/templates/single-input-with-double-button-submitters.html b/cypress/fixtures/templates/single-input-with-double-button-submitters.html deleted file mode 100644 index d70293e..0000000 --- a/cypress/fixtures/templates/single-input-with-double-button-submitters.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Title - - -
- - - -
- - diff --git a/cypress/fixtures/templates/single-input-with-double-input-submitters.html b/cypress/fixtures/templates/single-input-with-double-input-submitters.html deleted file mode 100644 index c25fa20..0000000 --- a/cypress/fixtures/templates/single-input-with-double-input-submitters.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Title - - -
- - - -
- - diff --git a/cypress/fixtures/templates/single-input.html b/cypress/fixtures/templates/single-input.html deleted file mode 100644 index 01adf54..0000000 --- a/cypress/fixtures/templates/single-input.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/templates/single-multiple-file-input.html b/cypress/fixtures/templates/single-multiple-file-input.html deleted file mode 100644 index 633b1fd..0000000 --- a/cypress/fixtures/templates/single-multiple-file-input.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/templates/single-multiple-select.html b/cypress/fixtures/templates/single-multiple-select.html deleted file mode 100644 index 08e64d2..0000000 --- a/cypress/fixtures/templates/single-multiple-select.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/templates/single-outside-input-and-submitter.html b/cypress/fixtures/templates/single-outside-input-and-submitter.html deleted file mode 100644 index 17b8808..0000000 --- a/cypress/fixtures/templates/single-outside-input-and-submitter.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - Title - - -
- - - - diff --git a/cypress/fixtures/templates/single-outside-input.html b/cypress/fixtures/templates/single-outside-input.html deleted file mode 100644 index 4553eb1..0000000 --- a/cypress/fixtures/templates/single-outside-input.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Title - - -
- -
- - - diff --git a/cypress/fixtures/templates/single-readonly-input.html b/cypress/fixtures/templates/single-readonly-input.html deleted file mode 100644 index 0088118..0000000 --- a/cypress/fixtures/templates/single-readonly-input.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/templates/single-select.html b/cypress/fixtures/templates/single-select.html deleted file mode 100644 index 1f71ded..0000000 --- a/cypress/fixtures/templates/single-select.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Title - - -
- - -
- - diff --git a/cypress/fixtures/uploads/data2.json b/cypress/fixtures/uploads/data2.json new file mode 100644 index 0000000..8b32dd1 --- /dev/null +++ b/cypress/fixtures/uploads/data2.json @@ -0,0 +1,3 @@ +{ + "hello": "hi" +} diff --git a/cypress/integration/blank.test.ts b/cypress/integration/blank.test.ts deleted file mode 100644 index b1bd3ea..0000000 --- a/cypress/integration/blank.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import getFormValues from '../../src' -import * as utils from '../../test/utils' - -describe('blank template', () => { - beforeEach(utils.setup('blank')) - - it('should have blank form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - {} - ); - }); -}) diff --git a/cypress/integration/checkbox.test.ts b/cypress/integration/checkbox.test.ts new file mode 100644 index 0000000..67fd9bb --- /dev/null +++ b/cypress/integration/checkbox.test.ts @@ -0,0 +1,82 @@ +import getFormValues from '../../src' +import * as utils from '../utils' + +describe('checkbox', () => { + describe('basic', () => { + beforeEach(utils.setup(` + + + + + Checkbox/Basic + + +
+ + +
+ + + `)) + + it('should have no form values', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const values = getFormValues(form, {submitter}) + const before = utils.makeSearchParams(values).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(values['enabled']).toBeUndefined(); + expect(before).toEqual(after); + }, + '' + ); + }); + + it('should have false checked value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const values = getFormValues(form, {submitter, booleanValuelessCheckbox: true }) + expect(values['enabled']).toBe(false); + } + ); + }); + }) + + describe('checked', () => { + beforeEach(utils.setup(` + + + + + Checkbox/Checked + + +
+ + +
+ + + `)) + + it('should have single form value on a single field', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + 'enabled=on' + ); + }); + }) +}) diff --git a/cypress/integration/everything.test.ts b/cypress/integration/everything.test.ts deleted file mode 100644 index cbf4d69..0000000 --- a/cypress/integration/everything.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import getFormValues from '../../src' -import * as utils from '../../test/utils'; - -describe('everything template', () => { - beforeEach(utils.setup('everything')) - - it('should have correct form values', () => { - utils.test( - (cy) => { - cy.get('[name="first_name"]').type('John') - cy.get('[name="middle_name"]').type('Marcelo') - cy.get('[name="last_name"]').type('Dela Cruz') - cy.get('[name="gender"][value="m"]').check() - cy.get('[name="civil_status"]').select('Married') - cy.get('[name="new_registration"]').check() - cy.get('[name="nationality"][value="filipino"]').check() - cy.get('[name="dependent"][value="Jun"]').check() - - // Note: JSDOM is static for now - cy.get('button.dependents').click() - cy.get('.additional-dependent [name="dependent"][type="text"]').last().type('Juana') - cy.get('button.dependents').click() - cy.get('.additional-dependent [name="dependent"][type="text"]').last().type('Jane') - cy.get('button.dependents').click() - cy.get('.additional-dependent [name="dependent"][type="text"]').last().type('Josh') - - cy.get('[name="notes"]').type('Test content\n\nNew line\n\nAnother line') - return cy.get('[name="submit"][value="Hi"]') - }, - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - 'first_name=John&middle_name=Marcelo&last_name=Dela+Cruz&gender=m&civil_status=married&new_registration=on&nationality=filipino&dependent=Jun¬es=Test+content%0D%0A%0D%0ANew+line%0D%0A%0D%0AAnother+line&submit=Hi', - ); - }); -}) diff --git a/cypress/integration/file.test.ts b/cypress/integration/file.test.ts new file mode 100644 index 0000000..56274af --- /dev/null +++ b/cypress/integration/file.test.ts @@ -0,0 +1,142 @@ +import getFormValues from '../../src' +import * as utils from '../utils' + +describe('file', () => { + describe('single', () => { + beforeEach(utils.setup(` + + + + + File/Single + + +
+ + +
+ + + `)) + + it('should have no form values when no file is selected', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + {} + ); + }) + + it('should have single form value when a file is selected', () => { + utils.test( + (cy: any) => { + cy + .get('[name="hello"]') + .attachFile('uploads/data.json') + + return cy.get('[type="submit"]') + }, + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'data.json', + } + ); + }) + + it('should retrieve the file list upon setting appropriate option', () => { + utils.test( + (cy: any) => { + cy + .get('[name="hello"]') + .attachFile('uploads/data.json') + + return cy.get('[type="submit"]') + }, + (form: HTMLFormElement, submitter: any) => { + const formValues = getFormValues(form, {submitter, getFileObjects: true}) + expect(formValues.hello[0].name).toBe('data.json') + //expect(before).toEqual(after); + }, + ); + }) + }) + + describe('multiple', () => { + beforeEach(utils.setup(` + + + + + File/Multiple + + +
+ + +
+ + + `)) + + it('should have no form values when no file is selected', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + {} + ); + }) + + it('should have single form value when a file is selected', () => { + utils.test( + (cy: any) => { + cy + .get('[name="hello"]') + .attachFile(['uploads/data.json', 'uploads/data2.json']) + + return cy.get('[type="submit"]') + }, + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + 'hello=data.json&hello=data2.json', + ); + }) + + it('should retrieve the file list upon setting appropriate option', () => { + utils.test( + (cy: any) => { + cy + .get('[name="hello"]') + .attachFile(['uploads/data.json', 'uploads/data2.json']) + + return cy.get('[type="submit"]') + }, + (form: HTMLFormElement, submitter: any) => { + const formValues = getFormValues(form, {submitter, getFileObjects: true}) + expect(formValues.hello[0].name).toBe('data.json') + expect(formValues.hello[1].name).toBe('data2.json') + }, + ); + }) + }) +}) diff --git a/cypress/integration/misc.test.ts b/cypress/integration/misc.test.ts new file mode 100644 index 0000000..2425e55 --- /dev/null +++ b/cypress/integration/misc.test.ts @@ -0,0 +1,168 @@ +import getFormValues from '../../src' +import * as utils from '../utils' + +describe('misc', () => { + describe('blank', () => { + beforeEach(utils.setup(` + + + + + Misc/Blank + + +
+ +
+ + + `)) + + it('should have blank form value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + {} + ); + }); + }) + + describe('everything', () => { + beforeEach(utils.setup(` + + + + + Misc/Everything + + +
+

+
+
+ +
+
+ +
+
+ +
+
+ Gender +
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ + Default Dependents + +
+ + +
+
+
+ +
+
+ +
+
+ + +
+
+
+ + + + `)) + + it('should have correct form values', () => { + utils.test( + (cy) => { + cy.get('[name="first_name"]').type('John') + cy.get('[name="middle_name"]').type('Marcelo') + cy.get('[name="last_name"]').type('Dela Cruz') + cy.get('[name="gender"][value="m"]').check() + cy.get('[name="civil_status"]').select('Married') + cy.get('[name="new_registration"]').check() + cy.get('[name="nationality"][value="filipino"]').check() + cy.get('[name="dependent"][value="Jun"]').check() + + // Note: JSDOM is static for now + cy.get('button.dependents').click() + cy.get('.additional-dependent [name="dependent"][type="text"]').last().type('Juana') + cy.get('button.dependents').click() + cy.get('.additional-dependent [name="dependent"][type="text"]').last().type('Jane') + cy.get('button.dependents').click() + cy.get('.additional-dependent [name="dependent"][type="text"]').last().type('Josh') + + cy.get('[name="notes"]').type('Test content\n\nNew line\n\nAnother line') + return cy.get('[name="submit"][value="Hi"]') + }, + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + 'first_name=John&middle_name=Marcelo&last_name=Dela+Cruz&gender=m&civil_status=married&new_registration=on&nationality=filipino&dependent=Jun¬es=Test+content%0D%0A%0D%0ANew+line%0D%0A%0D%0AAnother+line&submit=Hi', + ); + }); + }) +}) diff --git a/cypress/integration/select.test.ts b/cypress/integration/select.test.ts new file mode 100644 index 0000000..226df96 --- /dev/null +++ b/cypress/integration/select.test.ts @@ -0,0 +1,81 @@ +import getFormValues from '../../src' +import * as utils from '../utils' + +describe('select', () => { + describe('multiple', () => { + beforeEach(utils.setup(` + + + + + Select/Multiple + + +
+ + +
+ + + `)) + + it('should have multiple form values on a single field', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + 'hello=Bar&hello=Quux' + ); + }); + }) + + describe('single', () => { + beforeEach(utils.setup(` + + + + + Select/Single + + +
+ + +
+ + + `)) + + it('should have single form value on a single field', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Baz', + } + ); + }); + }) +}) diff --git a/cypress/integration/single.test.ts b/cypress/integration/single.test.ts deleted file mode 100644 index c82209a..0000000 --- a/cypress/integration/single.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -import getFormValues from '../../src' -import * as utils from '../../test/utils'; - -describe('single input template', () => { - beforeEach(utils.setup('single-input')) - - it('should have single form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Hi', - } - ); - }); -}) - -describe('single outside input template', () => { - beforeEach(utils.setup('single-outside-input')) - - it('should have single form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Hi', - } - ); - }); -}) - -describe('single outside input and submitter template', () => { - beforeEach(utils.setup('single-outside-input-and-submitter')) - - it('should have single form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Hi', - } - ); - }); -}) - -describe('single readonly template', () => { - beforeEach(utils.setup('single-readonly-input')) - - it('should have single form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Hi', - } - ); - }); -}) - -describe('single disabled template', () => { - beforeEach(utils.setup('single-disabled-input')) - - it('should have blank form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - {} - ); - }); -}) - -describe('single input with double button submitters template', () => { - beforeEach(utils.setup('single-input-with-double-button-submitters')) - - it('should have double form values', () => { - utils.test( - (cy: any) => cy.get('[name="action"][value="Foo"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Hi', - action: 'Foo', - } - ); - }); -}) - -describe('single input with double input submitters template', () => { - beforeEach(utils.setup('single-input-with-double-input-submitters')) - - it('should have double form values', () => { - utils.test( - (cy: any) => cy.get('[name="action"][value="Bar"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Hi', - action: 'Bar', - } - ); - }); -}) - -describe('single select template', () => { - beforeEach(utils.setup('single-select')) - - it('should have single form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Baz', - } - ); - }); -}) - -describe('single multiple select template', () => { - beforeEach(utils.setup('single-multiple-select')) - - it('should have single form value', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'Bar,Quux', - } - ); - }); -}) - -describe('single file input template', () => { - beforeEach(utils.setup('single-file-input')) - - it('should have no form values when no file is selected', () => { - utils.test( - (cy: any) => cy.get('[type="submit"]'), - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - {} - ); - }) - - it('should have single form value when a file is selected', () => { - utils.test( - (cy: any) => { - cy - .get('[name="hello"]') - .attachFile('uploads/data.json') - - return cy.get('[type="submit"]') - }, - (form: HTMLFormElement, submitter: any, search: any) => { - const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); - const after = utils.makeSearchParams(search).toString(); - expect(before).toEqual(after); - }, - { - hello: 'data.json', - } - ); - }) -}) diff --git a/cypress/integration/submitter.test.ts b/cypress/integration/submitter.test.ts new file mode 100644 index 0000000..433598b --- /dev/null +++ b/cypress/integration/submitter.test.ts @@ -0,0 +1,112 @@ +import getFormValues from '../../src' +import * as utils from '../utils' + +describe('submitter', () => { + describe('button', () => { + beforeEach(utils.setup(` + + + + + Submitter/Button + + +
+ + + +
+ + + `)) + + it('should have double form values', () => { + utils.test( + (cy: any) => cy.get('[name="action"][value="Foo"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Hi', + action: 'Foo', + } + ); + }); + }) + + describe('input', () => { + beforeEach(utils.setup(` + + + + + Submitter/Input + + +
+ + + +
+ + + `)) + + it('should have double form values', () => { + utils.test( + (cy: any) => cy.get('[name="action"][value="Bar"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Hi', + action: 'Bar', + } + ); + }); + }) + + describe('outside', () => { + beforeEach(utils.setup(` + + + + + Submitter/Outside + + +
+ + + + + `)) + + it('should have single form value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Hi', + } + ); + }); + }) +}) diff --git a/cypress/integration/text.test.ts b/cypress/integration/text.test.ts new file mode 100644 index 0000000..a5f5776 --- /dev/null +++ b/cypress/integration/text.test.ts @@ -0,0 +1,148 @@ +import getFormValues from '../../src' +import * as utils from '../utils' + +describe('text', () => { + describe('basic', () => { + beforeEach(utils.setup(` + + + + + Text/Basic + + +
+ + +
+ + + `)) + + it('should have single form value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Hi', + } + ); + }); + }) + + describe('disabled', () => { + beforeEach(utils.setup(` + + + + + Text/Disabled + + +
+ + +
+ + + `)) + + it('should have blank form value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + {} + ); + }); + }) + + describe('outside', () => { + beforeEach(utils.setup(` + + + + + Text/Outside + + +
+ +
+ + + + `)) + + it('should have single form value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Hi', + } + ); + }); + }) + + describe('readonly', () => { + beforeEach(utils.setup(` + + + + + Text/Readonly + + +
+ + +
+ + + `)) + + it('should have single form value', () => { + utils.test( + (cy: any) => cy.get('[type="submit"]'), + (form: HTMLFormElement, submitter: any, search: any) => { + const before = utils.makeSearchParams(getFormValues(form, {submitter})).toString(); + const after = utils.makeSearchParams(search).toString(); + expect(before).toEqual(after); + }, + { + hello: 'Hi', + } + ); + }); + }) +}) diff --git a/cypress/utils/index.ts b/cypress/utils/index.ts new file mode 100644 index 0000000..7204d66 --- /dev/null +++ b/cypress/utils/index.ts @@ -0,0 +1,95 @@ +/// + +import JSDOMDummyCypress from './jsdom-compat' + +type ExpectedSearchValue = Record | string + +type RetrieveSubmitterFn = (wrapper: any) => any + +type HTMLSubmitterElement = HTMLButtonElement | HTMLInputElement + +type TestFn = (form: HTMLFormElement, submitter: HTMLSubmitterElement, after: ExpectedSearchValue) => unknown + +export const setup = (template: string) => { + if (typeof cy !== 'undefined') { + return () => { + cy.intercept({ url: '/' }, { body: template }); + cy.intercept({ url: '/?*' }, { body: template }).as('submitted'); + } + } + return () => { + window.document.open(undefined, undefined, undefined, true) + window.document.write(template) + window.document.close() + } +} + +export const test = (retrieveSubmitterFn: RetrieveSubmitterFn, testFn: TestFn, expectedValue?: ExpectedSearchValue) => { + let form: HTMLFormElement + let submitter: HTMLButtonElement | HTMLInputElement + let r: any + if (typeof cy !== 'undefined') { + cy + .visit('/') + .get('form') + .then((formResult: any) => { + [form] = Array.from(formResult); + }) + + r = retrieveSubmitterFn(cy) + .then((submitterQueryEl: any) => { + [submitter] = Array.from(submitterQueryEl as any[]) + }) + + if (typeof expectedValue !== 'undefined') { + r.click() + cy + .wait('@submitted') + .location('search') + .then((search: any) => { + testFn(form, submitter, search) + }) + } else { + cy + .location('search') + .then((search: any) => { + testFn(form, submitter, search) + }) + } + } else { + r = retrieveSubmitterFn(new JSDOMDummyCypress()) + .then((submitterQueryEl: any) => { + [submitter] = Array.from(submitterQueryEl as any[]); + [form] = Array.from(window.document.getElementsByTagName('form')) + testFn(form, submitter, expectedValue) + }) + + if (typeof expectedValue !== 'undefined') { + r.click() + } + } +} + +export const makeSearchParams = (beforeValues: Record | string) => { + switch (typeof (beforeValues as unknown)) { + case 'string': + return new URLSearchParams(beforeValues as string) + case 'object': + return Object + .entries(beforeValues) + .filter(([k]) => k.trim().length > 0) + .reduce( + (beforeSearchParams, [key, value]) => { + const theValue = !Array.isArray(value) ? [value] : value + theValue.forEach(v => { + beforeSearchParams.append(key, v) + }) + return beforeSearchParams + }, + new URLSearchParams() + ) + default: + break + } + throw new TypeError('Invalid parameter.') +} diff --git a/cypress/utils/jsdom-compat.ts b/cypress/utils/jsdom-compat.ts new file mode 100644 index 0000000..1df5a20 --- /dev/null +++ b/cypress/utils/jsdom-compat.ts @@ -0,0 +1,74 @@ +/// + +import { readFileSync } from 'fs' +import { join } from 'path' + +class JSDOMJQuery { + private selectedElements: Node[] + constructor(elements: NodeList) { + this.selectedElements = Array.from(elements) + } + + type(s: string) { + this.selectedElements.forEach((el: any) => { + if (el.tagName === 'TEXTAREA') { + el.innerText = s + el.value = s + return + } + el.setAttribute('value', s) + el.value = s + }) + return this + } + + check() { + this.selectedElements.forEach((el: any) => { + el.setAttribute('checked', '') + el.checked = true + }) + return this + } + + select(v: string) { + this.selectedElements.forEach((el: any) => { + const option: any = Array.from(el.querySelectorAll('option')).find((o: any) => o.textContent === v) + option.setAttribute('selected', '') + el.value = option.value + }) + return this + } + + last() { + this.selectedElements = this.selectedElements.slice(-1) + return this + } + + then(fn: (...args: unknown[]) => unknown) { + fn(this.selectedElements) + return this + } + + click() { + return this + } + + submit() { + return this + } + + attachFile(filename: string) { + const contents = readFileSync(join('cypress', 'fixtures', filename)) + // TODO + contents.toString('binary') + return this + } +} + +export default class JSDOMDummyCypress { + private currentElement = window.document; + + get(q: string) { + return new JSDOMJQuery(this.currentElement.querySelectorAll(q)); + } +} diff --git a/package.json b/package.json index 272580a..46f82b7 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,13 @@ ], "publishing": { "github": { - "repository": "https://github.com/TheoryOfNekomata/formxtr.git", + "repository": "https://github.com/TheoryOfNekomata/formxtra.git", "publishConfig": { "registry": "https://npm.pkg.github.com" } }, "master": { - "repository": "https://code.modal.sh/TheoryOfNekomata/formxtr.git", + "repository": "https://code.modal.sh/TheoryOfNekomata/formxtra.git", "publishConfig": { "registry": "https://js.pack.modal.sh" } @@ -51,7 +51,7 @@ "singleQuote": true, "trailingComma": "es5" }, - "name": "@theoryofnekomata/formxtr", + "name": "@theoryofnekomata/formxtra", "author": "TheoryOfNekomata", "module": "dist/get-form-values.esm.js", "size-limit": [ diff --git a/src/index.ts b/src/index.ts index 5e04396..d6163c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,137 +1,285 @@ /** * Type for valid field elements. - * - * */ - -type HTMLFieldElement +export type HTMLFieldElement = HTMLInputElement | HTMLButtonElement | HTMLSelectElement | HTMLTextAreaElement +/** + * Line ending. + */ +export enum LineEnding { + /** + * Carriage return. + */ + CR = '\r', + /** + * Line feed. + */ + LF = '\n', + /** + * Carriage return/line feed combination. + */ + CRLF = '\r\n', +} + /** * Type for valid submitter elements. * - * Only the