Browse Source

cucumber-puppeteer tests added, close Popup on Esc, components reorganized

master
ChiefRed 3 years ago
parent
commit
452aa3fdf5
28 changed files with 6078 additions and 40 deletions
  1. +1
    -0
      cucumber.js
  2. +119
    -0
      features/feedback.feature
  3. +31
    -0
      features/hooks.js
  4. +55
    -0
      features/recover_password.feature
  5. +123
    -0
      features/register_user.feature
  6. +85
    -0
      features/sign_in.feature
  7. +78
    -0
      features/step_definitions/common.js
  8. +541
    -0
      features/support/actions.js
  9. +8
    -0
      features/support/config.js
  10. +10
    -0
      features/support/pages.js
  11. +1
    -0
      features/support/scope.js
  12. +43
    -0
      features/support/selectors.js
  13. +30
    -0
      features/world.js
  14. +4
    -0
      package.json
  15. +9
    -1
      react-formik-tailwind-labwork.code-workspace
  16. +3
    -3
      src/App.jsx
  17. +3795
    -0
      src/assets/styles/.index.css
  18. +4
    -1
      src/components/FormikControls/FormikControlErrorMessage.jsx
  19. +9
    -4
      src/components/Popup.jsx
  20. +3
    -3
      src/components/ServicePolicy.jsx
  21. +0
    -0
      src/components/SubmitFormResult.jsx
  22. +5
    -4
      src/components/forms/FormFeedback.jsx
  23. +2
    -2
      src/components/forms/FormLogin.jsx
  24. +2
    -2
      src/components/forms/FormPasswordRecovery.jsx
  25. +4
    -4
      src/components/forms/FormRegistration.jsx
  26. +15
    -0
      src/hooks/useKeypress.js
  27. +23
    -0
      tailwind.js
  28. +1075
    -16
      yarn.lock

+ 1
- 0
cucumber.js View File

@@ -0,0 +1 @@
module.exports = { default: '--publish-quiet' }

+ 119
- 0
features/feedback.feature View File

@@ -0,0 +1,119 @@
Feature: Leave a feedback

Background:
Given visit "Home" page
# clear input fields persistence mechanism (just speeds up the test)
Given clear browser storages
When press "Feedback" button at "Page content"
Then modal of "Some feedback form example" dialog opened


# Path of success

Scenario: Fulfill feedback form for user support

When enter "John Doe" in "Your name" input at "Feedback form"
* enter "test@example.com" in "Email" input at "Feedback form"
* choose "User support" in "Section" radio group at "Feedback form"
* set "Consulting on product" in "Required types of support" checkbox group at "Feedback form"
* enter "test" in "Message" textarea at "Feedback form"
Then button "Send" at "Feedback form" is enabled
When press "Send" button at "Feedback form"
Then wait for "0.5" seconds
* modal of "Some feedback form example" dialog closed
* modal of "Data submitted from the form" dialog opened
# technical field names are used
* jsonView field "user_name" contains "John Doe"
* jsonView field "email" contains "test@example.com"
* jsonView field "section" contains "users"
* jsonView field "support_type" contains row "0" => "product"
* jsonView field "message" contains "test"
* see "recaptcha" at "jsonView"



# Validations testing

Scenario: Feedback form validations

# prepare state of success
Given enter "John Doe" in "Your name" input at "Feedback form"
* enter "test@example.com" in "Email" input at "Feedback form"
* choose "Partnership" in "Section" radio group at "Feedback form"
* enter "test" in "Message" textarea at "Feedback form"
Then button "Send" at "Feedback form" is enabled

# do not see any error message
Then not see "Required" at "Error message"
* not see "Must be a valid email address" at "Error message"
* not see "Please fill all necessary form fields" at "Error message"

# Your name
When enter "" in "Your name" input at "Feedback form"
Then see "Required" at "Error message"
* button "Send" at "Feedback form" is disabled
When enter "Jane Doe" in "Your name" input at "Feedback form"
Then not see "Required" at "Error message"
* button "Send" at "Feedback form" is enabled

# Email
When enter "" in "Email" input at "Feedback form"
Then see "Required" at "Error message"
* button "Send" at "Feedback form" is disabled
When enter "test" in "Email" input at "Feedback form"
Then see "Must be a valid email address" at "Error message"
* button "Send" at "Feedback form" is disabled
When enter "test@example.com" in "Email" input at "Feedback form"
Then not see "Must be a valid email address" at "Error message"
* button "Send" at "Feedback form" is enabled

# Section (can not be unselected)
* not see "Required types of support" at "Feedback form"
When choose "User support" in "Section" radio group at "Feedback form"
Then see "Required types of support" at "Feedback form"
* not see "Required" at "Error message"
* button "Send" at "Feedback form" is disabled

# Required types of support (visible and required only if Section is User support)
When set "Consulting on product" in "Required types of support" checkbox group at "Feedback form"
Then not see "Required" at "Error message"
* button "Send" at "Feedback form" is enabled

When unset "Consulting on product" in "Required types of support" checkbox group at "Feedback form"
Then see "Required" at "Error message"
* button "Send" at "Feedback form" is disabled

When set "Technical support" in "Required types of support" checkbox group at "Feedback form"
Then not see "Required" at "Error message"
Then button "Send" at "Feedback form" is enabled

When unset "Technical support" in "Required types of support" checkbox group at "Feedback form"
Then see "Required" at "Error message"
* button "Send" at "Feedback form" is disabled

When set "Legal support" in "Required types of support" checkbox group at "Feedback form"
Then not see "Required" at "Error message"
* button "Send" at "Feedback form" is enabled

When set "Technical support" in "Required types of support" checkbox group at "Feedback form"
Then not see "Required" at "Error message"
Then button "Send" at "Feedback form" is enabled

* set "Consulting on product" in "Required types of support" checkbox group at "Feedback form"
Then not see "Required" at "Error message"
Then button "Send" at "Feedback form" is enabled

When unset "Technical support" in "Required types of support" checkbox group at "Feedback form"
Then not see "Required" at "Error message"
* button "Send" at "Feedback form" is enabled

# Message
When enter "" in "Message" input at "Feedback form"
Then see "Required" at "Error message"
* button "Send" at "Feedback form" is disabled
When enter "The test message" in "Message" input at "Feedback form"
Then not see "Required" at "Error message"
* button "Send" at "Feedback form" is enabled

# reCaptcha badge replacement warning
* see "This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply." at "Feedback form"

+ 31
- 0
features/hooks.js View File

@@ -0,0 +1,31 @@
/*eslint no-console: ["error", { allow: ["log"] }] */
// Dependencies
const { After, Before, AfterAll } = require('@cucumber/cucumber');
const config = require('./support/config');
const scope = require('./support/scope');

// Here is where you might clean up database tables to have a clean slate before the tests run
// Before(async () => {
// });

// Here we clean up the browser session
After(async () => {
if (scope.browser && scope.context.page) {
const cookies = await scope.context.page.cookies();
if (cookies && cookies.length > 0) {
await scope.context.page.deleteCookie(...cookies);
}
if(config.close) {
await scope.context.page.close();
scope.context.page = null;
}
}
});

AfterAll(async () => {
if (config.close && scope.browser) await scope.browser.close();

// If you have your API and Web servers running, you can shut them down afterwards
// scope.api.shutdown(() => console.log('\nAPI is shut down'));
// scope.web.shutdown(() => console.log('Web is shut down'));
});

+ 55
- 0
features/recover_password.feature View File

@@ -0,0 +1,55 @@
Feature: Recover password

Background:
Given visit "Home" page
# clear input fields persistence mechanism (just speeds up the test)
Given clear browser storages
When press "Sign In" button at "Page content"
Then modal of "Sign in the restricted area" dialog opened
When click "Forgot password?" link at "Sign In form"
Then modal of "Sign in the restricted area" dialog closed
* modal of "Forgotten password recovery" dialog opened


# Path of success

Scenario: Fulfill password recovery form

When enter "test@example.com" in "Email" input at "Password recovery form"
Then button "Recover" at "Password recovery form" is enabled
When press "Recover" button at "Password recovery form"
Then modal of "Sign in the restricted area" dialog closed
* modal of "Data submitted from the form" dialog opened
# technical field names are used
* jsonView field "email" contains "test@example.com"


Scenario: Switch to Sign In dialogue

When press "Go to Sign In" button at "Password recovery form"
Then modal of "Forgotten password recovery" dialog closed
* modal of "Sign in the restricted area" dialog opened


# Validations testing

Scenario: Password recovery form validations

# prepare state of success
Given enter "test@example.com" in "Email" input at "Password recovery form"
* button "Recover" at "Password recovery form" is enabled

Then not see "Required" at "Error message"
* not see "Must be a valid email address" at "Error message"

When enter "" in "Email" input at "Password recovery form"
Then see "Required" at "Error message"
* button "Recover" at "Password recovery form" is disabled

When enter "test" in "Email" input at "Password recovery form"
Then see "Must be a valid email address" at "Error message"
* button "Recover" at "Password recovery form" is disabled

When enter "test@example.com" in "Email" input at "Password recovery form"
Then not see "Must be a valid email address" at "Error message"
* button "Recover" at "Password recovery form" is enabled

+ 123
- 0
features/register_user.feature View File

@@ -0,0 +1,123 @@
Feature: Register new user

Background:
Given visit "Home" page
# clear input fields persistence mechanism (just speeds up the test)
Given clear browser storages
When press "Registration" button at "Page content"
Then modal of "New user registration" dialog opened


# Path of success

# Scenario: Fulfill user registration form (simple)

# # For cases where we have no selector collisions in the DOM

# When enter "test@example.com" in "Email" input
# * enter "password1234" in "Password" input
# * enter "password1234" in "Confirm password" input
# * set "Accept Service Policy" checkbox
# Then button "Register" is enabled
# When press "Register" button
# Then modal of "New user registration" dialog closed
# * modal of "Data submitted from the form" dialog opened
# # technical field names are used
# * jsonView field "Email" contains "test@example.com"
# * jsonView field "Password" contains "password1234"
# * jsonView field "Confirm password" contains "password1234"
# * jsonView field "Accept Service Policy" contains "true"


Scenario: Fulfill user registration form

When enter "test@example.com" in "Email" input at "User registration form"
* enter "password1234" in "Password" input at "User registration form"
* enter "password1234" in "Confirm password" input at "User registration form"
* set "Accept Service Policy" checkbox at "User registration form"
Then button "Register" at "User registration form" is enabled
When press "Register" button at "User registration form"
Then modal of "New user registration" dialog closed
* modal of "Data submitted from the form" dialog opened
# technical field names are used
* jsonView field "email" contains "test@example.com"
* jsonView field "password" contains "password1234"
* jsonView field "password_confirm" contains "password1234"
* jsonView field "userconsent" contains "true"


Scenario: Switch to Sign In dialogue

When press "Have account? - Sign In" button at "User registration form"
Then modal of "New user registration" dialog closed
* modal of "Sign in the restricted area" dialog opened


Scenario: Switch to Service Policy dialogue... and back

When click "Service Policy" link at "User registration form"
Then modal of "New user registration" dialog closed
* modal of "Service Policy" dialog opened
Then button "Go to Registration" at "Service Policy form" is enabled
When press "Go to Registration" button at "Service Policy form"
Then modal of "Service Policy" dialog closed
* modal of "New user registration" dialog opened


# Validations testing

Scenario: Registration form validations

# prepare state of success
Given enter "test@example.com" in "Email" input at "User registration form"
* enter "password1234" in "Password" input at "User registration form"
* enter "password1234" in "Confirm password" input at "User registration form"
* set "Accept Service Policy" checkbox at "User registration form"
* button "Register" at "User registration form" is enabled

# do not see any error message
Then not see "Required" at "Error message"
* not see "Must be a valid email address" at "Error message"
* not see "Minimum 8 symbols" at "Error message"
* not see "Not matching with password" at "Error message"

# Email
When enter "" in "Email" input at "User registration form"
Then see "Required" at "Error message"
* button "Register" at "User registration form" is disabled
When enter "test" in "Email" input at "User registration form"
Then see "Must be a valid email address" at "Error message"
* button "Register" at "User registration form" is disabled
When enter "test@example.com" in "Email" input at "User registration form"
Then not see "Must be a valid email address" at "Error message"
* button "Register" at "User registration form" is enabled

# Password
When enter "" in "Password" input at "User registration form"
Then see "Required" at "Error message"
* button "Register" at "User registration form" is disabled
When enter "test" in "Password" input at "User registration form"
Then see "Minimum 8 symbols" at "Error message"
* button "Register" at "User registration form" is disabled
When enter "password1234" in "Password" input at "User registration form"
Then not see "Minimum 8 symbols" at "Error message"
* button "Register" at "User registration form" is enabled

# Confirm password
When enter "" in "Confirm password" input at "User registration form"
Then see "Required" at "Error message"
* button "Register" at "User registration form" is disabled
When enter "test" in "Confirm password" input at "User registration form"
Then see "Not matching with password" at "Error message"
* button "Register" at "User registration form" is disabled
When enter "password1234" in "Confirm password" input at "User registration form"
Then not see "Not matching with password" at "Error message"
* button "Register" at "User registration form" is enabled

# Accept Service Policy
When unset "Accept Service Policy" checkbox at "User registration form"
Then see "Required" at "Error message"
* button "Register" at "User registration form" is disabled
When set "Accept Service Policy" checkbox at "User registration form"
Then not see "Required" at "Error message"
* button "Register" at "User registration form" is enabled

+ 85
- 0
features/sign_in.feature View File

@@ -0,0 +1,85 @@
Feature: Sign In user

Background:
Given visit "Home" page
# clear input fields persistence mechanism (just speeds up the test)
Given clear browser storages
When press "Sign In" button at "Page content"
Then modal of "Sign in the restricted area" dialog opened


# Path of success

Scenario: Fulfill user Sign In form with Remember me set
When enter "test@example.com" in "Email" input at "Sign In form"
* enter "password1234" in "Password" input at "Sign In form"
* set "Remember me" checkbox at "Sign In form"
Then button "Sign In" at "Sign In form" is enabled
When press "Sign In" button at "Sign In form"
Then modal of "Sign in the restricted area" dialog closed
* modal of "Data submitted from the form" dialog opened
# technical field names are used
* jsonView field "email" contains "test@example.com"
* jsonView field "password" contains "password1234"
* jsonView field "remember_me" contains "true"


Scenario: Fulfill user Sign In form with Remember me unset

When enter "test@example.com" in "Email" input at "Sign In form"
* enter "password1234" in "Password" input at "Sign In form"
* unset "Remember me" checkbox at "Sign In form"
Then button "Sign In" at "Sign In form" is enabled
When press "Sign In" button at "Sign In form"
Then modal of "Sign in the restricted area" dialog closed
* modal of "Data submitted from the form" dialog opened
# technical field names are used
* jsonView field "email" contains "test@example.com"
* jsonView field "password" contains "password1234"
* jsonView field "remember_me" contains "false"


Scenario: Switch to Register new user dialogue

When press "Have no account? - Create" button at "Sign In form"
Then modal of "Sign in the restricted area" dialog closed
* modal of "New user registration" dialog opened


Scenario: Switch to Forgotten password recovery dialogue

When click "Forgot password?" link at "Sign In form"
Then modal of "Sign in the restricted area" dialog closed
* modal of "Forgotten password recovery" dialog opened


# Validations testing

Scenario: Sign In form validations

# prepare state of success
Given enter "test@example.com" in "Email" input at "Sign In form"
* enter "password1234" in "Password" input at "Sign In form"
* button "Sign In" at "Sign In form" is enabled

Then not see "Required" at "Error message"
* not see "Must be a valid email address" at "Error message"

When enter "" in "Email" input at "Sign In form"
Then see "Required" at "Error message"
* button "Sign In" at "Sign In form" is disabled

When enter "test" in "Email" input at "Sign In form"
Then see "Must be a valid email address" at "Error message"
* button "Sign In" at "Sign In form" is disabled

When enter "test@example.com" in "Email" input at "Sign In form"
Then not see "Must be a valid email address" at "Error message"
* button "Sign In" at "Sign In form" is enabled

When enter "" in "Password" input at "Sign In form"
Then see "Required" at "Error message"
* button "Sign In" at "Sign In form" is disabled
When enter "password1234" in "Password" input at "Sign In form"
Then not see "Required" at "Error message"
* button "Sign In" at "Sign In form" is enabled

+ 78
- 0
features/step_definitions/common.js View File

@@ -0,0 +1,78 @@
const { Given, When, Then } = require("@cucumber/cucumber")
const {
wait,
clearStorages,
visitPage,
visitPageIncognito,
shouldSeeText,
shouldSeeTextAt,
shouldNotSeeText,
shouldNotSeeTextAt,
clickLink,
clickLinkAt,
pressButton,
pressButtonAt,
modalOpened,
modalClosed,
buttonShouldBeDisabled,
buttonShouldBeEnabled,
buttonShouldBeDisabledAt,
buttonShouldBeEnabledAt,
fillInputField,
setCheckboxField,
unsetCheckboxField,
fillInputFieldAt,
setCheckboxFieldAt,
unsetCheckboxFieldAt,
chooseRadioOptionAt,
setCheckboxGroupOptionAt,
unsetCheckboxGroupOptionAt,
jsonViewContains,
jsonViewContainsAt,
} = require('../support/actions');


Given('clear browser storages', clearStorages);

Then('wait for {string} seconds', { timeout: 60 * 1000 }, wait);

Given('visit {string} page', visitPage);
Given('visit {string} page incognito', visitPageIncognito);

When('see {string}', shouldSeeText);
When('not see {string}', shouldNotSeeText);
When('see {string} at {string}', shouldSeeTextAt);
When('not see {string} at {string}', shouldNotSeeTextAt);


When('press {string} button', pressButton);
When('press {string} button at {string}', pressButtonAt);

When('click {string} link', clickLink);
When('click {string} link at {string}', clickLinkAt);

Then('modal of {string} dialog opened', modalOpened);
Then('modal of {string} dialog closed', modalClosed);

Then('button {string} is disabled', buttonShouldBeDisabled);
Then('button {string} is enabled', buttonShouldBeEnabled);

Then('button {string} at {string} is disabled', buttonShouldBeDisabledAt);
Then('button {string} at {string} is enabled', buttonShouldBeEnabledAt);

Given('enter {string} in {string} input', fillInputField);
Given('enter {string} in {string} textarea at {string}', fillInputFieldAt);

When('set {string} checkbox', setCheckboxField);
When('unset {string} checkbox', unsetCheckboxField);

When('enter {string} in {string} input at {string}', fillInputFieldAt);
When('set {string} checkbox at {string}', setCheckboxFieldAt);
When('unset {string} checkbox at {string}', unsetCheckboxFieldAt);

Given('choose {string} in {string} radio group at {string}', chooseRadioOptionAt);
Given('set {string} in {string} checkbox group at {string}', setCheckboxGroupOptionAt);
Given('unset {string} in {string} checkbox group at {string}', unsetCheckboxGroupOptionAt);

Then('jsonView field {string} contains {string}', jsonViewContains);
Then('jsonView field {string} contains row {string} => {string}', jsonViewContainsAt);

+ 541
- 0
features/support/actions.js View File

@@ -0,0 +1,541 @@
const config = require('./config');
const assert = require('assert');
const scope = require('./scope');
const pages = require('./pages');
const selectors = require('./selectors');

const pending = callback => {
callback(null, 'pending');
};

const delay = duration => new Promise(resolve => setTimeout(resolve, duration));

const wait = async timeInSeconds => {
const time = Math.round(parseFloat((''+timeInSeconds).replace(',', '.')) * 1000);
await delay(time);
};

const clearStorages = async () => {
if (scope.context && scope.context.page) {
await scope.context.page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
}
}

const visitPage = async (page) => {
if (!scope.browser) {
scope.browser = await scope.driver.launch({
headless: config.headless,
slowMo: config.slowMo,
args: [
],
});
}
scope.context.page = await scope.browser.newPage();
scope.context.page.setViewport({ width: 1280, height: 1024 });
const url = scope.host + pages[page];
const visit = await scope.context.page.goto(url, {
waitUntil: 'networkidle2'
});
return visit;
};

const visitPageIncognito = async (page) => {
if (!scope.browser) {
scope.browser = await scope.driver.launch({
headless: config.headless,
slowMo: config.slowMo,
args: [
'--incognito',
],
});
}
scope.context.page = await scope.browser.newPage();
scope.context.page.setViewport({ width: 1280, height: 1024 });
const url = scope.host + pages[page];
const visit = await scope.context.page.goto(url, {
waitUntil: 'networkidle2'
});
return visit;
};

const clickLink = async (text) => {
const { page } = scope.context;
const [link] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`);
if (link) {
await link.click();
} else {
throw new Error(
`Can not find link with text '${text}'`
);
}
};

const clickLinkAt = async (text, wrapper) => {
const { page } = scope.context;
const [link] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`);
if (link) {
await link.click();
} else {
throw new Error(
`Can not find link with text '${text}' in the wrapper ${wrapper}`
);
}
};

const pressButton = async text => {
const { page } = scope.context;
const [button] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`);
if (button) {
await button.click();
} else {
throw new Error(
`Can not find button with text '${text}'`
);
}
};

const pressButtonAt = async (text, wrapper) => {
const { page } = scope.context;
const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`);
if (button) {
await button.click();
} else {
throw new Error(
`Can not find button with text '${text}' in the wrapper ${wrapper}`
);
}
};

const modalOpened = async text => {
await delay(250);
const { page } = scope.context;
const waitModal = await page.waitForSelector('.modal');
if (!waitModal) {
throw new Error(
`Can not find modal`
);
}
await waitModal;
const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`);
if (!modalWithTitle) {
throw new Error(
`Can not find modal with title '${text}'`
);
}
}

const modalClosed = async text => {
await delay(250);
const { page } = scope.context;
const [modal] = await page.$x(`//*[contains(@class, 'modal')]`);
if (modal) {
const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`);
if (modalWithTitle) {
throw new Error(
`Modal with title '${text}' still opened`
);
}
}
}

const shouldSeeText = async text => {
const { page } = scope.context;
const content = await page.content();
if (!content.includes(text))
throw new Error(
`Expected page to contain text: ${text}, but page contains only: ${content}`
);
};

const shouldSeeTextAt = async (text, wrapper) => {
const { page } = scope.context;
const [el] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//*[contains(., '${text}')]`);
if (!el) {
const content = await page.content();
throw new Error(
`Expected wrapper ${wrapper} to contain text: ${text}, but page contains only: ${content}`
);
}
};

const shouldNotSeeText = async text => {
const { page } = scope.context;
const content = await page.content();
if (content.includes(text))
throw new Error(
`Expected page to not contain text: ${text}, but page contains: ${content}`
);
};

const shouldNotSeeTextAt = async (text, wrapper) => {
const { page } = scope.context;
const [el] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//*[contains(., '${text}')]`);
if (el) {
const content = await page.content();
throw new Error(
`Expected wrapper ${wrapper} to not contain text: ${text}, but page contains: ${content}`
);
}
};

const buttonShouldBeDisabled = async text => {
const { page } = scope.context;
const [button] = await page.$x(`//button[contains(., '${text}')]`);
if (button) {
let valueHandle = await button.getProperty('disabled');
assert.strictEqual(await valueHandle.jsonValue(), true);
} else {
throw new Error(
`Can not find page button with text '${text}'`
);
}
};

const buttonShouldBeEnabled = async text => {
const { page } = scope.context;
const [button] = await page.$x(`//button[contains(., '${text}')]`);
if (button) {
let valueHandle = await button.getProperty('disabled');
assert.strictEqual(await valueHandle.jsonValue(), false);
} else {
throw new Error(
`Can not find page button with text '${text}'`
);
}
};

const buttonShouldBeDisabledAt = async (text, wrapper) => {
const { page } = scope.context;
const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`);
if (button) {
let valueHandle = await button.getProperty('disabled');
assert.strictEqual(await valueHandle.jsonValue(), true);
} else {
throw new Error(
`Can not find page button with text '${text}' in the wrapper ${wrapper}`
);
}
};

const buttonShouldBeEnabledAt = async (text, wrapper) => {
const { page } = scope.context;
const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`);
if (button) {
let valueHandle = await button.getProperty('disabled');
assert.strictEqual(await valueHandle.jsonValue(), false);
} else {
throw new Error(
`Can not find page button with text '${text}' in the wrapper ${wrapper}`
);
}
};

const fillInputField = async (value, field) => {
const { page } = scope.context;
await shouldSeeText(field);
const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
await fieldPresent;
await page.focus(`input[name='${field}']`);
const inputValue = await page.$eval(selectors.inputs[field], el => el.value);
for (let i = 0; i < inputValue.length; i++) {
await page.keyboard.press('Backspace');
}
await page.type(selectors.inputs[field], value, { delay: 1 });
await page.$eval(selectors.inputs[field], e => e.blur());
return;
};


const fillInputFieldAt = async (value, field, wrapper) => {
const { page } = scope.context;
await shouldSeeText(field);
const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
await fieldPresent;
await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
const inputValue = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.value);
for (let i = 0; i < inputValue.length; i++) {
await page.keyboard.press('Backspace');
}
await page.type(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, value, { delay: 1 });
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
return;
};

const setCheckboxField = async (field) => {
const { page } = scope.context;
await shouldSeeText(field);
const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
await fieldPresent;
const isChecked = await page.$eval(selectors.inputs[field], el => el.checked)
if (!isChecked) {
await page.focus(selectors.inputs[field]);
await page.$eval(selectors.inputs[field], check => check.click());
await page.$eval(selectors.inputs[field], e => e.blur());
}
return;
};

const unsetCheckboxField = async (field) => {
const { page } = scope.context;
await shouldSeeText(field);
const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
await fieldPresent;
const isChecked = await page.$eval(selectors.inputs[field], el => el.checked)
if (isChecked) {
await page.focus(selectors.inputs[field]);
await page.$eval(selectors.inputs[field], check => check.click());
await page.$eval(selectors.inputs[field], e => e.blur());
}
return;
};

const setCheckboxFieldAt = async (field, wrapper) => {
const { page } = scope.context;
await shouldSeeText(field);
const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
await fieldPresent;
const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked)
if (!isChecked) {
await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click());
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
}
return;
};

const unsetCheckboxFieldAt = async (field, wrapper) => {
const { page } = scope.context;
await shouldSeeText(field);
const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
await fieldPresent;
const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked)
if (isChecked) {
await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click());
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
}
return;
};

const chooseRadioOption = async (option, field) => {
const { page } = scope.context;
await shouldSeeText(field);
if (!selectors.radio_groups[field]) {
throw new Error(
`Radio group '${field}' has no registered selector`
);
}
await shouldSeeText(option);
if (!selectors.radio_groups[field].options[option]) {
throw new Error(
`Radio group '${field}' has no registered option ${option}`
);
}
const isChecked = await page.$eval(`${selectors.radio_groups[field].options[option]}`, el => el.checked)
if (!isChecked) {
await page.focus(`${selectors.radio_groups[field].options[option]}`);
await page.$eval(`${selectors.radio_groups[field].options[option]}`, check => check.click());
await page.$eval(`${selectors.radio_groups[field].options[option]}`, e => e.blur());
}
};

const chooseRadioOptionAt = async (option, field, wrapper) => {
const { page } = scope.context;
await shouldSeeText(field);
if (!selectors.radio_groups[field]) {
throw new Error(
`Radio group '${field}' has no registered selector`
);
}
await shouldSeeText(option);
if (!selectors.radio_groups[field].options[option]) {
throw new Error(
`Radio group '${field}' has no registered option ${option}`
);
}
const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, el => el.checked)
if (!isChecked) {
await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`);
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, check => check.click());
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, e => e.blur());
}
};

const setCheckboxGroupOption = async (option, field) => {
const { page } = scope.context;
await shouldSeeText(field);
if (!selectors.checkbox_groups[field]) {
throw new Error(
`Checkbox group '${field}' has no registered selector`
);
}
await shouldSeeText(option);
if (!selectors.checkbox_groups[field].options[option]) {
throw new Error(
`Checkbox group '${field}' has no registered option ${option}`
);
}
const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
if (!isChecked) {
await page.focus(`${selectors.checkbox_groups[field].options[option]}`);
await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click());
await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
}
};

const unsetCheckboxGroupOption = async (option, field) => {
const { page } = scope.context;
await shouldSeeText(field);
if (!selectors.checkbox_groups[field]) {
throw new Error(
`Checkbox group '${field}' has no registered selector`
);
}
await shouldSeeText(option);
if (!selectors.checkbox_groups[field].options[option]) {
throw new Error(
`Checkbox group '${field}' has no registered option ${option}`
);
}
const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
if (isChecked) {
await page.focus(`${selectors.checkbox_groups[field].options[option]}`);
await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click());
await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
}
};

const setCheckboxGroupOptionAt = async (option, field, wrapper) => {
const { page } = scope.context;
await shouldSeeText(field);
if (!selectors.checkbox_groups[field]) {
throw new Error(
`Checkbox group '${field}' has no registered selector`
);
}
await shouldSeeText(option);
if (!selectors.checkbox_groups[field].options[option]) {
throw new Error(
`Checkbox group '${field}' has no registered option ${option}`
);
}
const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
if (!isChecked) {
await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`);
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click());
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
}
};

const unsetCheckboxGroupOptionAt = async (option, field, wrapper) => {
const { page } = scope.context;
await shouldSeeText(field);
if (!selectors.checkbox_groups[field]) {
throw new Error(
`Checkbox group '${field}' has no registered selector`
);
}
await shouldSeeText(option);
if (!selectors.checkbox_groups[field].options[option]) {
throw new Error(
`Checkbox group '${field}' has no registered option ${option}`
);
}
const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
if (isChecked) {
await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`);
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click());
await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
}
};

const jsonViewContains = async (field, value) => {
const { page } = scope.context;
const jsonViewPresent = await page.waitForSelector('.react-json-view');
await jsonViewPresent;
const aRes = await page.$$eval('.variable-row', options => options.map(option => option.textContent))
const keyWrap = isNaN(field) ? '"' : ''
const valWrap = isNaN(value) ? '"' : ''
let res = false;
aRes.forEach(e => {
if ('true' === value || 'false' === value) {
if (e.includes(keyWrap + field + keyWrap) && e.includes('bool' + value)) {
res = true;
}
} else {
if (e.includes(keyWrap + field + keyWrap) && e.includes(valWrap + value + valWrap)) {
res = true;
}
}
});
if (!res) {
throw new Error(
`Expected jsonView var: ${field} should contains ${value}, but given ${aRes}`
);
}
};

const jsonViewContainsAt = async (fieldName, rowKey, rowValue) => {
const { page } = scope.context;
const [fieldWrapper] = await page.$x(`//*[contains(@class, 'react-json-view')]//*[contains(., '${fieldName}')]//preceding::*[contains(@class, 'object-key-val')][1]`);
const aRes = await fieldWrapper.$$eval('.variable-row', options => options.map(option => option.textContent))
const keyWrap = isNaN(rowKey) ? '"' : ''
const valWrap = isNaN(rowValue) ? '"' : ''
let res = false;
aRes.forEach(e => {
if ('true' === rowValue || 'false' === rowValue) {
if (e.includes(keyWrap + rowKey + keyWrap) && e.includes('bool' + rowValue)) {
res = true;
}
} else {
if (e.includes(keyWrap + rowKey + keyWrap) && e.includes(valWrap + rowValue + valWrap)) {
res = true;
}
}
});
if (!res) {
throw new Error(
`Expected jsonView var: ${fieldName} should contains: ${rowKey} => ${rowValue}, but given ${aRes}`
);
}
};

module.exports = {
pending,
delay,
wait,
clearStorages,
visitPage,
visitPageIncognito,
shouldSeeText,
shouldSeeTextAt,
shouldNotSeeText,
shouldNotSeeTextAt,
clickLink,
clickLinkAt,
pressButton,
pressButtonAt,
buttonShouldBeDisabled,
buttonShouldBeEnabled,
buttonShouldBeDisabledAt,
buttonShouldBeEnabledAt,
fillInputField,
setCheckboxField,
unsetCheckboxField,
fillInputFieldAt,
setCheckboxFieldAt,
unsetCheckboxFieldAt,
chooseRadioOption,
chooseRadioOptionAt,
setCheckboxGroupOption,
unsetCheckboxGroupOption,
setCheckboxGroupOptionAt,
unsetCheckboxGroupOptionAt,
modalOpened,
modalClosed,
jsonViewContains,
jsonViewContainsAt,
};

+ 8
- 0
features/support/config.js View File

@@ -0,0 +1,8 @@
module.exports = {
port: 8989,

//headless: false,
slowMo: 0,

close: true,
};

+ 10
- 0
features/support/pages.js View File

@@ -0,0 +1,10 @@
const pages = {
Home: '/',
// login: '/login',
// signup: '/signup',
// 'forgot-password': '/forgot-password',
// account: '/account',
// 'reset-password': token => `/reset-password/${token}`
};

module.exports = pages;

+ 1
- 0
features/support/scope.js View File

@@ -0,0 +1 @@
module.exports = {};

+ 43
- 0
features/support/selectors.js View File

@@ -0,0 +1,43 @@
// These are ways of being able to identify HTML elements to interact with and check.
const selectors = {
wrappers: { // class names!!!
'Page content': 'page-content',
'Sign In form': 'sign-in-form',
'User registration form': 'registration-form',
'Password recovery form': 'password-recovery-form',
'Service Policy form': 'service-policy-form',
'Feedback form': 'feedback-form',
'Error message': 'error',
'jsonView': 'react-json-view',
},
inputs: {
'Your name': 'input[name="user_name"]',
'Email': 'input[name="email"]',
'Password': 'input[name="password"]',
'Confirm password': 'input[name="password_confirm"]',
'Accept Service Policy': 'input[type=checkbox][name="userconsent"]',
'Remember me': 'input[type=checkbox][name="remember_me"]',
'Message': 'textarea[name="message"]',
},
radio_groups: {
'Section': {
//name: 'section',
options: {
'User support': 'input[type=radio][name=section][value=users]',
'Partnership': 'input[type=radio][name=section][value=partners]',
}
},
},
checkbox_groups: {
'Required types of support': {
//name: 'support_type',
options: {
'Consulting on product': 'input[type=checkbox][name=support_type][value=product]',
'Technical support': 'input[type=checkbox][name=support_type][value=technical]',
'Legal support': 'input[type=checkbox][name=support_type][value=legal]',
}
}
},
};

module.exports = selectors;

+ 30
- 0
features/world.js View File

@@ -0,0 +1,30 @@
const config = require('./support/config');
const { setWorldConstructor } = require('@cucumber/cucumber');
const puppeteer = require('puppeteer');
const scope = require('./support/scope');

const express = require('express');
const httpShutdown = require('http-shutdown');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, '..', 'build')));

app.get('/', (req, res)=> {
res.sendFile(path.join(__dirname, '..', 'build', 'index.html'));
})

const web = httpShutdown(app.listen(config.port, () => {
console.log(`Web is listening on port ${config.port}`);
}));

web.host = `http://localhost:${config.port}`;

const World = function () {
scope.host = web.host;
scope.driver = puppeteer;
scope.context = {};
scope.web = web;
};

setWorldConstructor(World);

+ 4
- 0
package.json View File

@@ -8,6 +8,7 @@
"formik": "^2.1.7",
"formik-persist": "^1.1.0",
"framer-motion": "^2.7.7",
"http-shutdown": "^1.2.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-google-recaptcha": "^2.1.0",
@@ -21,6 +22,7 @@
"start": "yarn build:css && react-scripts start",
"build": "export NODE_ENV=production && yarn build:css && react-scripts build",
"test": "react-scripts test",
"acct": "NODE_ENV=test npx cucumber-js --no-strict --fail-fast",
"eject": "react-scripts eject",
"build:css": "postcss src/assets/styles/index.tailwind.css -o src/assets/styles/.index.css",
"watch:css": "postcss -w src/assets/styles/index.tailwind.css -o src/assets/styles/.index.css"
@@ -41,12 +43,14 @@
]
},
"devDependencies": {
"@cucumber/cucumber": "^7.0.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"autoprefixer": "9.8.6",
"postcss-cli": "^8.0.0",
"prop-types": "^15.7.2",
"puppeteer": "^5.5.0",
"react-scripts": "3.4.3"
}
}

+ 9
- 1
react-formik-tailwind-labwork.code-workspace View File

@@ -2,7 +2,15 @@
"folders": [
{
"path": "."
},
{
"path": "../../cucumber-and-puppeteer"
},
{
"path": "../../vodb/www/local/templates/akatan/react"
}
],
"settings": {}
"settings": {
"liveServer.settings.multiRootWorkspaceName": "react-formik-tailwind-labwork"
}
}

+ 3
- 3
src/App.jsx View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react'

import ButtonPrimary from './components/controls/ButtonPrimary'
import Popup from './Popup';
import Popup from './components/Popup';

import { FormLogin } from './components/forms/FormLogin'
import { FormRegistration } from './components/forms/FormRegistration';
@@ -33,8 +33,8 @@ const App = () => {

return (
<>
<div className="w-full h-full min-h-screen bg-white text-gray-900 text-lg flex items-center">
<div className="max-w-lg w-full mx-auto py-8">
<div className="w-full h-full min-h-screen bg-white text-gray-900 text-lg flex items-center font-gilroy">
<div className="page-content max-w-lg w-full mx-auto py-8">
<div className="my-4">
<ButtonPrimary text="Sign In" action={doOpenLoginPopup} />
</div>

+ 3795
- 0
src/assets/styles/.index.css
File diff suppressed because it is too large
View File


+ 4
- 1
src/components/FormikControls/FormikControlErrorMessage.jsx View File

@@ -3,7 +3,10 @@ import React from 'react'
function FormikControlErrorMessage(props) {
const {name, label, className, children, ...rest} = props
const classNames = ['error text-right text-sm text-red-700 mt-1 mr-1', className].join(' ');
return <div className={classNames} {...rest}><span className="sr-only">{label} field validation error message: </span>{children}</div>
return <div className={classNames} {...rest}>
<span className="sr-only">{label} field validation error message: </span>
<span>{children}</span>
</div>
}

export default FormikControlErrorMessage

src/Popup.jsx → src/components/Popup.jsx View File

@@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { motion, AnimatePresence } from "framer-motion"
import { RemoveScroll } from 'react-remove-scroll'
import useKeypress from '../hooks/useKeypress';

export const Popup = (props) => {
const { opened, title, close, children } = props
@@ -26,10 +27,14 @@ export const Popup = (props) => {
},
}

useKeypress('Escape', () => {
opened && close && close()
});

return <>
<AnimatePresence>
{opened &&
<div className="modal z-50 fixed w-full min-h-screen top-0 left-0 flex items-center justify-center">
<div className="modal z-50 fixed w-full min-h-screen top-0 left-0 flex items-center justify-center select-none font-gilroy">
<div className="modal-overlay absolute top-0 left-0 w-full h-full bg-primary-900 opacity-50 backdrop-blur"></div>
<div className="modal-wrapper relative w-full">
<div className="max-w-full max-h-screen sm:max-w-lg mx-auto">
@@ -39,10 +44,10 @@ export const Popup = (props) => {
animate="visible"
exit="hidden"
>
<div tabIndex="0" aria-label={title} aria-modal className="ma-0 bg-gray-200 shadow-lg border-white border-t-2 border-b-2 sm:border-2 sm:mx-4 sm:rounded-lg focus:outline-none" onClick={e => e.stopPropagation()}>
<div tabIndex="0" aria-label={title} aria-modal className="bg-gray-200 shadow-lg border-white border-t-2 border-b-2 sm:border-2 sm:mx-4 sm:rounded-lg focus:outline-none" onClick={e => e.stopPropagation()}>
<div className="px-3 h-12 bg-primary-800 text-white border-b sm:rounded-t-lg border-white font-semibold relative flex items-center">
<div className="w-full text-xl truncate pr-6">{title}</div>
<button type="button" aria-label="Close modal" className="absolute top-0 right-0 w-12 h-12 pb-1 text-3xl leading-none cursor-pointer flex items-center justify-center transform origin-center hover:scale-125 active:scale-110 focus:outline-none" onClick={close}>&times;</button>
<div className="modal-title w-full text-xl truncate pr-6">{title}</div>
<button type="button" aria-label="Close modal" className="absolute top-1 right-1 w-10 h-10 text-4xl leading-none cursor-pointer flex items-center justify-center transform origin-center hover:scale-125 active:scale-110 focus:outline-none" onClick={close} title="Close (Esc)">&times;</button>
</div>
<RemoveScroll removeScrollBar={false}>
{children}

src/ServicePolicy.jsx → src/components/ServicePolicy.jsx View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import ButtonSecondary from './components/controls/ButtonSecondary'
import { FormRegistration } from './components/forms/FormRegistration'
import ButtonSecondary from './controls/ButtonSecondary'
import { FormRegistration } from './forms/FormRegistration'

export function ServicePolicy(props) {
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props
@@ -27,7 +27,7 @@ export function ServicePolicy(props) {
<p>Facere ex error molestiae maxime ullam dolor blanditiis dolores fuga ipsam vero distinctio, ipsum delectus harum optio quo repudiandae unde beatae rerum laborum cumque, quisquam quia quod?</p>
<p>Numquam praesentium ex recusandae quia tenetur, quod, illum officiis magni est alias consequatur maiores accusamus ipsam provident sequi dolores excepturi exercitationem vitae aspernatur perferendis porro sit accusantium aut? Tempora nesciunt, autem dignissimos sit ratione dolores odit amet laborum nihil corporis id nobis tempore! Quam rerum repudiandae expedita voluptate error dolorum a. Obcaecati fugiat magnam earum?</p>
</div>
<div className="p-2 mb-0 bg-primary-200 border-t rounded-b-lg">
<div className="service-policy-form p-2 mb-0 bg-primary-200 border-t rounded-b-lg">
<ButtonSecondary type="button" text="Go to Registration" action={goRegistration} />
</div>
</>

src/SubmitFormResult.jsx → src/components/SubmitFormResult.jsx View File


+ 5
- 4
src/components/forms/FormFeedback.jsx View File

@@ -7,7 +7,7 @@ import * as Yup from 'yup'
import ReCAPTCHA from "react-google-recaptcha";
import FormikControl from '../FormikControls'
import ButtonPrimary from '../controls/ButtonPrimary'
import { SubmitFormResult } from '../../SubmitFormResult';
import { SubmitFormResult } from '../SubmitFormResult';

export const FormFeedback = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened } = props
@@ -158,7 +158,7 @@ export const FormFeedback = (props) => {
{formik => {
return (
<div className="overflow-y-auto max-h-screen-80">
<Form autoComplete="off">
<Form className="feedback-form" autoComplete="off">
<div className="p-3">
<FormikControl
control='string'
@@ -209,9 +209,10 @@ export const FormFeedback = (props) => {
{(!formik.isValid || !formik.dirty)
? <div className="error text-center text-sm text-red-700 mt-1 mr-1">Please fill all necessary form fields</div>
: <div className="text-gray-900 text-center text-sm mt-1 mr-1">
<div>This site is protected by reCAPTCHA and the Google</div>
<div>This site is protected by reCAPTCHA and the Google </div>
<div>
<a href="https://policies.google.com/privacy" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Privacy Policy</a> and
<a href="https://policies.google.com/privacy" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Privacy Policy</a>
<span> and </span>
<a href="https://policies.google.com/terms" className="mx-1 hover:underline" rel="noopener noreferrer nofollow" target="_blank">Terms of Service</a> apply.
</div>
</div>

+ 2
- 2
src/components/forms/FormLogin.jsx View File

@@ -9,7 +9,7 @@ import ButtonSecondary from '../controls/ButtonSecondary'
import Link from '../controls/Link'
import { FormRegistration } from './FormRegistration'
import { FormPasswordRecovery } from './FormPasswordRecovery';
import { SubmitFormResult } from '../../SubmitFormResult';
import { SubmitFormResult } from '../SubmitFormResult';

export const FormLogin = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props
@@ -65,7 +65,7 @@ export const FormLogin = (props) => {
{formik => {
return (
<div className="overflow-y-auto max-h-screen-80">
<Form>
<Form className="sign-in-form">
<div className="p-3">
<FormikControl
control='string'

+ 2
- 2
src/components/forms/FormPasswordRecovery.jsx View File

@@ -7,7 +7,7 @@ import FormikControl from '../FormikControls'
import ButtonPrimary from '../controls/ButtonPrimary'
import ButtonSecondary from '../controls/ButtonSecondary'
import { FormLogin } from './FormLogin'
import { SubmitFormResult } from '../../SubmitFormResult';
import { SubmitFormResult } from '../SubmitFormResult';

export const FormPasswordRecovery = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props
@@ -51,7 +51,7 @@ export const FormPasswordRecovery = (props) => {
{formik => {
return (
<div className="overflow-y-auto max-h-screen-80">
<Form>
<Form className="password-recovery-form">
<div className="p-3">
<FormikControl
control='string'

+ 4
- 4
src/components/forms/FormRegistration.jsx View File

@@ -7,8 +7,8 @@ import FormikControl from '../FormikControls'
import ButtonPrimary from '../controls/ButtonPrimary'
import ButtonSecondary from '../controls/ButtonSecondary'
import { FormLogin } from './FormLogin'
import { ServicePolicy } from '../../ServicePolicy'
import { SubmitFormResult } from '../../SubmitFormResult';
import { ServicePolicy } from '../ServicePolicy'
import { SubmitFormResult } from '../SubmitFormResult';

export const FormRegistration = (props) => {
const { setPopupTitle, setPopupContent, setPopupOpened, close } = props
@@ -71,7 +71,7 @@ export const FormRegistration = (props) => {
{formik => {
return (
<div className="overflow-y-auto max-h-screen-80">
<Form>
<Form className="registration-form">
<div className="p-3">
<FormikControl
control='string'
@@ -94,7 +94,7 @@ export const FormRegistration = (props) => {
<FormikControl
control='password'
name='password_confirm'
label='Conform password'
label='Confirm password'
placeholder='Enter same password'
required
aria-required="true"

+ 15
- 0
src/hooks/useKeypress.js View File

@@ -0,0 +1,15 @@
import { useEffect } from 'react';
/**
* useKeyPress
* @param {string} key - the name of the key to respond to, compared against event.key
* @param {function} action - the action to perform on key press
*/
export default function useKeypress(key, action) {
useEffect(() => {
function onKeyup(e) {
if (e.key === key) action()
}
window.addEventListener('keyup', onKeyup);
return () => window.removeEventListener('keyup', onKeyup);
});
}

+ 23
- 0
tailwind.js View File

@@ -327,7 +327,30 @@ module.exports = {
}),
inset: {
'0': '0',
'1': '0.25rem',
'2': '0.5rem',
'3': '0.75rem',
'4': '1rem',
'5': '1.25rem',
'6': '1.5rem',
'7': '1.75rem',
'8': '2rem',
'9': '2.25rem',
'10': '2.5rem',
'12': '3rem',
'16': '4rem',
'20': '5rem',
'24': '6rem',
'32': '8rem',
'40': '10rem',
'48': '12rem',
'56': '14rem',
'64': '16rem',
auto: 'auto',
'1/2': '50%',
'1/3': '33%',
'1/4': '25%',
'3/4': '75%',
},
letterSpacing: {
tighter: '-0.05em',

+ 1075
- 16
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save