From 339a2307324bc6c339317cb58f2298849f6923db Mon Sep 17 00:00:00 2001 From: = Date: Wed, 25 Nov 2020 13:58:31 -0500 Subject: [PATCH 01/12] Working on sign up specs --- cypress/integration/bridgeapi/sign_up.spec.js | 102 +++++++++++++++++- package-lock.json | 6 +- package.json | 4 +- pages/users/signup.js | 22 ++-- 4 files changed, 117 insertions(+), 17 deletions(-) diff --git a/cypress/integration/bridgeapi/sign_up.spec.js b/cypress/integration/bridgeapi/sign_up.spec.js index 34f1c19..2c46ca3 100644 --- a/cypress/integration/bridgeapi/sign_up.spec.js +++ b/cypress/integration/bridgeapi/sign_up.spec.js @@ -1,14 +1,108 @@ /// +const stubSuccessSignUp = () => { + cy.server(); + cy.route({ + url: '/user', + method: 'POST', + status: 201, + response: { + user: { + email: 'demo@demo.com', + notifications: false, + }, + }, + }); +}; + +const stubFailSignUp = () => { + cy.server(); + cy.route({ + url: '/user', + method: 'POST', + status: 422, + response: { + error: 'email or password is invalid', + }, + }); +}; + +const stubSuccessLogin = () => { + cy.server(); + cy.route({ + url: '/login', + method: 'POST', + status: 201, + response: { + token: '123984790182347', + }, + }); +}; + +const stubFailLogin = () => { + cy.server(); + cy.route({ + url: '/login', + method: 'POST', + status: 422, + response: { + token: '123984790182347', + }, + }); +}; + +const inputFields = () => { + cy.get('#email-input') + .type('demo@demo.com').should('have.value', 'demo@demo.com'); + + cy.get('#password-input') + .type('password').should('have.value', 'password'); + + cy.get('#password-confirmation-input') + .type('password').should('have.value', 'password'); +}; + +const submit = () => { + cy.get('form').submit(); +}; + describe('Sign Up', () => { beforeEach(() => { cy.visit('/users/signup'); }); - // context('' () => { + it('can sign up & login', () => { + stubSuccessSignUp(); + stubSuccessLogin(); + inputFields(); + submit(); + + cy.get('.MuiAlert-message').contains('Account has been created. Redirecting...'); + cy.location().should((location) => { + expect(location.href).to.eq('http://localhost:3000/bridge/new'); + }); + }); + + it('can handle failed sign up', () => { + stubFailSignUp(); + inputFields(); + submit(); + + cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); + cy.location().should((location) => { + expect(location.href).to.eq('http://localhost:3000/users/signup'); + }); + }); + + it('can handle failed login', () => { + stubSuccessSignUp(); + stubFailLogin(); + inputFields(); + submit(); - // }) - it('is true', () => { - expect(true).to.equal(true); + cy.get('.MuiAlert-message').contains('Account has been created. Redirecting...'); + cy.location().should((location) => { + expect(location.href).to.eq('http://localhost:3000/users/login'); + }); }); }); diff --git a/package-lock.json b/package-lock.json index 48a3cc9..c2d0bfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3436,9 +3436,9 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, "cypress": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-5.6.0.tgz", - "integrity": "sha512-cs5vG3E2JLldAc16+5yQxaVRLLqMVya5RlrfPWkC72S5xrlHFdw7ovxPb61s4wYweROKTyH01WQc2PFzwwVvyQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-6.0.0.tgz", + "integrity": "sha512-A/w9S15xGxX5UVeAQZacKBqaA0Uqlae9e5WMrehehAdFiLOZj08IgSVZOV8YqA9OH9Z0iBOnmsEkK3NNj43VrA==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "^0.4.1", diff --git a/package.json b/package.json index 2e2e216..7b26975 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "typeface-open-sans": "^1.1.13" }, "devDependencies": { - "cypress": "^5.6.0", + "cypress": "^6.0.0", "eslint": "^7.12.0", "eslint-config-airbnb": "^18.2.0", "eslint-plugin-cypress": "^2.11.2", @@ -44,4 +44,4 @@ "eslint-plugin-react-hooks": "^4.2.0", "start-server-and-test": "^1.11.6" } -} \ No newline at end of file +} diff --git a/pages/users/signup.js b/pages/users/signup.js index 1d7c6f3..9e23b60 100644 --- a/pages/users/signup.js +++ b/pages/users/signup.js @@ -77,7 +77,7 @@ function Signup() { const { login } = useAuth(); const handleSubmit = async (values, setSubmitting) => { - await api.post('/user', { + const res = await api.post('/user', { user: { email: values.email, password: values.password, @@ -87,13 +87,15 @@ function Signup() { setErrorOpen(true); }); - setSuccessOpen(true); - if (await login(values.email, values.password)) { - router.push('/bridge/new'); - } else { - // Ideally we send users to `/bridge/new` but if an error occurs - // lets at least send them to the login page. - router.push('/users/login'); + if (res) { + setSuccessOpen(true); + if (await login(values.email, values.password)) { + router.push('/bridge/new'); + } else { + // Ideally we send users to `/bridge/new` but if an error occurs + // lets at least send them to the login page. + router.push('/users/login'); + } } setSubmitting(false); @@ -121,6 +123,7 @@ function Signup() { onSubmit={(values, { setSubmitting }) => handleSubmit(values, setSubmitting)} validateOnBlur={false} validateOnChange={false} + id="form" > {({ values, submitForm, isSubmitting }) => (
@@ -135,6 +138,7 @@ function Signup() { name="email" value={values.email} style={{ marginBottom: 20 }} + id="email-input" /> From c6e518a83a06037deb61a0172be108cbec7ce18a Mon Sep 17 00:00:00 2001 From: = Date: Wed, 25 Nov 2020 14:34:28 -0500 Subject: [PATCH 02/12] Clean up stubbing --- cypress/integration/bridgeapi/sign_up.spec.js | 56 ++++++------------- cypress/support/commands.js | 9 +++ 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/cypress/integration/bridgeapi/sign_up.spec.js b/cypress/integration/bridgeapi/sign_up.spec.js index 2c46ca3..2708bde 100644 --- a/cypress/integration/bridgeapi/sign_up.spec.js +++ b/cypress/integration/bridgeapi/sign_up.spec.js @@ -1,54 +1,34 @@ /// const stubSuccessSignUp = () => { - cy.server(); - cy.route({ - url: '/user', - method: 'POST', - status: 201, - response: { - user: { - email: 'demo@demo.com', - notifications: false, - }, + const response = { + user: { + email: 'demo@demo.com', + notifications: false, }, - }); + }; + cy.stubRequest('/user', 'POST', 201, response); }; const stubFailSignUp = () => { - cy.server(); - cy.route({ - url: '/user', - method: 'POST', - status: 422, - response: { - error: 'email or password is invalid', - }, - }); + const response = { + error: 'email or password is invalid', + }; + cy.stubRequest('/user', 'POST', 422, response); }; const stubSuccessLogin = () => { - cy.server(); - cy.route({ - url: '/login', - method: 'POST', - status: 201, - response: { - token: '123984790182347', - }, - }); + const response = { + token: '123984790182347', + }; + cy.stubRequest('/login', 'POST', 201, response); }; const stubFailLogin = () => { - cy.server(); - cy.route({ - url: '/login', - method: 'POST', - status: 422, - response: { - token: '123984790182347', - }, - }); + const response = { + token: '123984790182347', + }; + cy.stubRequest('/login', 'POST', 422, response); }; const inputFields = () => { diff --git a/cypress/support/commands.js b/cypress/support/commands.js index ca4d256..81f8954 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,3 +23,12 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) +Cypress.Commands.add('stubRequest', (url, method, status, response) => { + cy.server(); + cy.route({ + url, + method, + status, + response, + }); +}); \ No newline at end of file From f57e74d06fdecf64b82ad3bbdaa81bea561e710e Mon Sep 17 00:00:00 2001 From: = Date: Wed, 25 Nov 2020 18:28:51 -0500 Subject: [PATCH 03/12] Cypress specs' --- .../integration/bridgeapi/users/login.spec.js | 64 +++++++++++++++++++ .../bridgeapi/{ => users}/sign_up.spec.js | 7 +- pages/dashboard.js | 1 + pages/users/login.js | 20 +++++- 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 cypress/integration/bridgeapi/users/login.spec.js rename cypress/integration/bridgeapi/{ => users}/sign_up.spec.js (85%) diff --git a/cypress/integration/bridgeapi/users/login.spec.js b/cypress/integration/bridgeapi/users/login.spec.js new file mode 100644 index 0000000..78434cf --- /dev/null +++ b/cypress/integration/bridgeapi/users/login.spec.js @@ -0,0 +1,64 @@ +/// + +const stubSuccessLogin = () => { + const response = { + token: '123984790182347', + }; + cy.stubRequest('/login', 'POST', 201, response); +}; + +const stubFailLogin = () => { + const response = { + token: '123984790182347', + }; + cy.stubRequest('/login', 'POST', 422, response); +}; + +const stubDashboard = () => { + const response = { + bridges: [], + }; + cy.stubRequest('/bridges', 'GET', 200, response); +}; + +const inputFields = () => { + cy.get('#email-input') + .type('demo@demo.com').should('have.value', 'demo@demo.com'); + + cy.get('#password-input') + .type('password').should('have.value', 'password'); +}; + +const submit = () => { + cy.get('form').submit(); +}; + +describe('Login', () => { + beforeEach(() => { + cy.visit('/users/login'); + }); + + it('can login', () => { + stubSuccessLogin(); + stubDashboard(); + inputFields(); + submit(); + + // TODO: Test snackbar + // cy.get('.MuiAlert-message').contains('Account has been created. Redirecting...'); + cy.location({ timeout: 10000 }).should((location) => { + expect(location.pathname).to.eq('/dashboard'); + }); + }); + + // it('can handle failed login', () => { + // stubFailLogin(); + // inputFields(); + // submit(); + + // // cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); + // cy.location().should((location) => { + // expect(location.pathname).to.eq('/users/login'); + // }); + // }); +}); diff --git a/cypress/integration/bridgeapi/sign_up.spec.js b/cypress/integration/bridgeapi/users/sign_up.spec.js similarity index 85% rename from cypress/integration/bridgeapi/sign_up.spec.js rename to cypress/integration/bridgeapi/users/sign_up.spec.js index 2708bde..ba78504 100644 --- a/cypress/integration/bridgeapi/sign_up.spec.js +++ b/cypress/integration/bridgeapi/users/sign_up.spec.js @@ -59,7 +59,7 @@ describe('Sign Up', () => { cy.get('.MuiAlert-message').contains('Account has been created. Redirecting...'); cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:3000/bridge/new'); + expect(location.pathname).to.eq('/bridge/new'); }); }); @@ -70,7 +70,7 @@ describe('Sign Up', () => { cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:3000/users/signup'); + expect(location.pathname).to.eq('/users/signup'); }); }); @@ -80,9 +80,8 @@ describe('Sign Up', () => { inputFields(); submit(); - cy.get('.MuiAlert-message').contains('Account has been created. Redirecting...'); cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:3000/users/login'); + expect(location.pathname).to.eq('/users/login'); }); }); }); diff --git a/pages/dashboard.js b/pages/dashboard.js index d40e7f2..8bb468e 100644 --- a/pages/dashboard.js +++ b/pages/dashboard.js @@ -58,6 +58,7 @@ export default Dashboard; export async function getServerSideProps(context) { const res = await fetchDataOrRedirect(context, '/bridges'); + console.log(res); if (!res) return { props: {} }; // Redirecting to /users/login return { diff --git a/pages/users/login.js b/pages/users/login.js index f4d0852..faa8f58 100644 --- a/pages/users/login.js +++ b/pages/users/login.js @@ -1,3 +1,4 @@ +// TODO: Change to snackbar import { useState } from 'react'; import { Container, @@ -67,7 +68,21 @@ function Login() { if (await login(values.email, values.password)) { setFormMessage('Success: Logging in. Please wait.'); - router.push('/dashboard'); + // router.push('/dashboard'); + // + // TODO: Nextjs doesn't support Server side redirects + // with client side router pushes. If we push to dashboard, + // then dashboards `getServerSideProps` returns 4XX, the app + // will crash with error: + // Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client + // + // To remedy this, I believe it would be best to get rid of + // the ssrRedirect protection and rely on the client side + // protection. + // + // window.location causes a full refresh which solves the issue. + debugger; + window.location.pathname = '/dashboard'; } else { setFormMessage('Error: Email or password is invalid'); setSubmitting(false); @@ -92,6 +107,7 @@ function Login() { initialValues={initialValues} validate={(values) => handleValidate(values)} onSubmit={(values, { setSubmitting }) => handleSubmit(values, setSubmitting)} + id="form" > {({ submitForm, isSubmitting, values, @@ -107,6 +123,7 @@ function Login() { label="Email" value={values.email} style={{ marginBottom: '25px', width: '100%' }} + id="email-input" /> From 157d402bfdb02fcc7c9bd75edea0a8e9606f065e Mon Sep 17 00:00:00 2001 From: = Date: Wed, 25 Nov 2020 19:03:11 -0500 Subject: [PATCH 04/12] Integrate mock service worker for stubbing node requests --- .../integration/bridgeapi/users/login.spec.js | 18 +- mocks/browser.js | 6 + mocks/handlers.js | 9 + mocks/index.js | 8 + mocks/server.js | 6 + package-lock.json | 259 ++++++++++++++++++ package.json | 1 + pages/_app.js | 8 + pages/users/login.js | 1 - public/mockServiceWorker.js | 241 ++++++++++++++++ 10 files changed, 547 insertions(+), 10 deletions(-) create mode 100644 mocks/browser.js create mode 100644 mocks/handlers.js create mode 100644 mocks/index.js create mode 100644 mocks/server.js create mode 100644 public/mockServiceWorker.js diff --git a/cypress/integration/bridgeapi/users/login.spec.js b/cypress/integration/bridgeapi/users/login.spec.js index 78434cf..f921488 100644 --- a/cypress/integration/bridgeapi/users/login.spec.js +++ b/cypress/integration/bridgeapi/users/login.spec.js @@ -51,14 +51,14 @@ describe('Login', () => { }); }); - // it('can handle failed login', () => { - // stubFailLogin(); - // inputFields(); - // submit(); + it('can handle failed login', () => { + stubFailLogin(); + inputFields(); + submit(); - // // cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); - // cy.location().should((location) => { - // expect(location.pathname).to.eq('/users/login'); - // }); - // }); + // cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); + cy.location().should((location) => { + expect(location.pathname).to.eq('/users/login'); + }); + }); }); diff --git a/mocks/browser.js b/mocks/browser.js new file mode 100644 index 0000000..3e077b7 --- /dev/null +++ b/mocks/browser.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { setupWorker } from 'msw'; +import handlers from './handlers'; + +// eslint-disable-next-line import/prefer-default-export +export const worker = setupWorker(...handlers); diff --git a/mocks/handlers.js b/mocks/handlers.js new file mode 100644 index 0000000..2781f31 --- /dev/null +++ b/mocks/handlers.js @@ -0,0 +1,9 @@ +import { rest } from 'msw'; +// import users from 'data/users'; // contains mock data for users +// import messages from 'data/messages';// contains mock data for messages + +const handlers = [ + rest.get('http://localhost:3001/bridges', (req, res, ctx) => res(ctx.json([]))), +]; + +export default handlers; diff --git a/mocks/index.js b/mocks/index.js new file mode 100644 index 0000000..5176398 --- /dev/null +++ b/mocks/index.js @@ -0,0 +1,8 @@ +/* eslint-disable global-require */ +if (typeof window === 'undefined') { + const { server } = require('./server'); + server.listen(); +} else { + const { worker } = require('./browser'); + worker.start(); +} diff --git a/mocks/server.js b/mocks/server.js new file mode 100644 index 0000000..a0df561 --- /dev/null +++ b/mocks/server.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { setupServer } from 'msw/node'; +import handlers from './handlers'; + +// eslint-disable-next-line import/prefer-default-export +export const server = setupServer(...handlers); diff --git a/package-lock.json b/package-lock.json index c2d0bfd..960f177 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1699,6 +1699,12 @@ } } }, + "@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", + "dev": true + }, "@samverschueren/stream-to-observable": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz", @@ -1729,6 +1735,12 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", "dev": true }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", + "dev": true + }, "@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", @@ -2996,6 +3008,77 @@ } } }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -5227,6 +5310,12 @@ } } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -5310,6 +5399,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, + "graphql": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.4.0.tgz", + "integrity": "sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -5452,6 +5547,12 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "headers-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz", + "integrity": "sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7020,6 +7121,83 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "msw": { + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/msw/-/msw-0.22.3.tgz", + "integrity": "sha512-rrxRf/6XEjvtL4rkwgxh7CG8Wn+sebHKt+GW4UBLhLRoMeeAG0zwGZGVYvDDbx6od/cKFEqCbEAb2JCk1dah8Q==", + "dev": true, + "requires": { + "@open-draft/until": "^1.0.3", + "@types/cookie": "^0.4.0", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cookie": "^0.4.1", + "graphql": "^15.4.0", + "headers-utils": "^1.2.0", + "node-fetch": "^2.6.1", + "node-match-path": "^0.6.0", + "node-request-interceptor": "^0.5.3", + "statuses": "^2.0.0", + "yargs": "^16.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "nan": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", @@ -7269,11 +7447,28 @@ } } }, + "node-match-path": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/node-match-path/-/node-match-path-0.6.0.tgz", + "integrity": "sha512-mld1LbiLaufULAYFPAWgNEG4P0ccL49otlL/nbF5VBQLATuzfS1BGYV1rjRMsxbc0vcnasikFqGHoKDFMQylMw==", + "dev": true + }, "node-releases": { "version": "1.1.65", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.65.tgz", "integrity": "sha512-YpzJOe2WFIW0V4ZkJQd/DGR/zdVwc/pI4Nl1CZrBO19FdRcSTmsuhdttw9rsTzzJLrNcSloLiBbEYx1C4f6gpA==" }, + "node-request-interceptor": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.5.4.tgz", + "integrity": "sha512-Pfej8crRk2muP76e0tfMG5FLoHG1gBz9pX/a2xPeSkkLMZRDbxocwiIfZIG91PZpx3XzmQNRsGiX0F5XA0DrUw==", + "dev": true, + "requires": { + "@open-draft/until": "^1.0.3", + "debug": "^4.1.1", + "headers-utils": "^1.2.0" + } + }, "nookies": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/nookies/-/nookies-2.5.0.tgz", @@ -8436,6 +8631,12 @@ "throttleit": "^1.0.0" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, "resolve": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", @@ -9146,6 +9347,12 @@ } } }, + "statuses": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.0.tgz", + "integrity": "sha512-w9jNUUQdpuVoYqXxnyOakhckBbOxRaoYqJscyIBYCS5ixyCnO7nQn7zBZvP9zf5QOPZcz2DLUpE3KsNPbJBOFA==", + "dev": true + }, "stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -10623,6 +10830,58 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yargs": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.1.tgz", + "integrity": "sha512-hAD1RcFP/wfgfxgMVswPE+z3tlPFtxG8/yWUrG2i17sTWGCGqWnxKcLTF4cUKDUK8fzokwsmO9H0TDkRbMHy8w==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 7b26975..e1a0641 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.21.5", "eslint-plugin-react-hooks": "^4.2.0", + "msw": "^0.22.3", "start-server-and-test": "^1.11.6" } } diff --git a/pages/_app.js b/pages/_app.js index 685cb06..fa3cb4f 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -9,6 +9,14 @@ import theme from '../src/theme'; import '../styles/Loader.css'; import AuthProvider from '../src/contexts/auth'; +// If TEST_ENV is truthy, include mocks in the build. +// This is strictly for testing. Can't do NODE_ENV as +// `next build` overwrites NODE_ENV with production. +if (process.env.TEST_ENV) { + // eslint-disable-next-line global-require + require('../mocks'); +} + export default function MyApp(props) { const { Component, pageProps } = props; React.useEffect(() => { diff --git a/pages/users/login.js b/pages/users/login.js index faa8f58..d922608 100644 --- a/pages/users/login.js +++ b/pages/users/login.js @@ -81,7 +81,6 @@ function Login() { // protection. // // window.location causes a full refresh which solves the issue. - debugger; window.location.pathname = '/dashboard'; } else { setFormMessage('Error: Email or password is invalid'); diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 0000000..ae51ff0 --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,241 @@ +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ +/* eslint-disable */ +/* tslint:disable */ + +const INTEGRITY_CHECKSUM = '65d33ca82955e1c5928aed19d1bdf3f9' +const bypassHeaderName = 'x-msw-bypass' + +let clients = {} + +self.addEventListener('install', function () { + return self.skipWaiting() +}) + +self.addEventListener('activate', async function (event) { + return self.clients.claim() +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + const client = await event.currentTarget.clients.get(clientId) + const allClients = await self.clients.matchAll() + const allClientIds = allClients.map((client) => client.id) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break + } + + case 'MOCK_ACTIVATE': { + clients = ensureKeys(allClientIds, clients) + clients[clientId] = true + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }) + break + } + + case 'MOCK_DEACTIVATE': { + clients = ensureKeys(allClientIds, clients) + clients[clientId] = false + break + } + + case 'CLIENT_CLOSED': { + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { clientId, request } = event + const requestClone = request.clone() + const getOriginalResponse = () => fetch(requestClone) + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Bypass mocking if the current client isn't present in the internal clients map + // (i.e. has the mocking disabled). + if (!clients[clientId]) { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + event.respondWith( + new Promise(async (resolve, reject) => { + const client = await event.target.clients.get(clientId) + + // Bypass mocking when the request client is not active. + if (!client) { + return resolve(getOriginalResponse()) + } + + // Bypass requests with the explicit bypass header + if (requestClone.headers.get(bypassHeaderName) === 'true') { + const modifiedHeaders = serializeHeaders(requestClone.headers) + + // Remove the bypass header to comply with the CORS preflight check + delete modifiedHeaders[bypassHeaderName] + + const originalRequest = new Request(requestClone, { + headers: new Headers(modifiedHeaders), + }) + + return resolve(fetch(originalRequest)) + } + + const reqHeaders = serializeHeaders(request.headers) + const body = await request.text() + + const rawClientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + url: request.url, + method: request.method, + headers: reqHeaders, + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body, + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }) + + const clientMessage = rawClientMessage + + switch (clientMessage.type) { + case 'MOCK_SUCCESS': { + setTimeout( + resolve.bind(this, createResponse(clientMessage)), + clientMessage.payload.delay, + ) + break + } + + case 'MOCK_NOT_FOUND': { + return resolve(getOriginalResponse()) + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.payload + const networkError = new Error(message) + networkError.name = name + + // Rejecting a request Promise emulates a network error. + return reject(networkError) + } + + case 'INTERNAL_ERROR': { + const parsedBody = JSON.parse(clientMessage.payload.body) + + console.error( + `\ +[MSW] Request handler function for "%s %s" has thrown the following exception: + +${parsedBody.errorType}: ${parsedBody.message} +(see more detailed error stack trace in the mocked response body) + +This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error. +If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ + `, + request.method, + request.url, + ) + + return resolve(createResponse(clientMessage)) + } + } + }).catch((error) => { + console.error( + '[MSW] Failed to mock a "%s" request to "%s": %s', + request.method, + request.url, + error, + ) + }), + ) +}) + +function serializeHeaders(headers) { + const reqHeaders = {} + headers.forEach((value, name) => { + reqHeaders[name] = reqHeaders[name] + ? [].concat(reqHeaders[name]).concat(value) + : value + }) + return reqHeaders +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + reject(event.data.error) + } else { + resolve(event.data) + } + } + + client.postMessage(JSON.stringify(message), [channel.port2]) + }) +} + +function createResponse(clientMessage) { + return new Response(clientMessage.payload.body, { + ...clientMessage.payload, + headers: clientMessage.payload.headers, + }) +} + +function ensureKeys(keys, obj) { + return Object.keys(obj).reduce((acc, key) => { + if (keys.includes(key)) { + acc[key] = obj[key] + } + + return acc + }, {}) +} From 42f4e10662f2cdde882d481d2316d6e531b5cd76 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 25 Nov 2020 20:50:29 -0500 Subject: [PATCH 05/12] Support cypress stubbing with #intercept --- .github/workflows/ci.yml | 2 +- .../integration/bridgeapi/users/login.spec.js | 20 +++++++++---------- cypress/support/commands.js | 11 ++++------ pages/users/login.js | 1 - utils/api.js | 16 ++++++++++++--- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c13f52a..4304b69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,4 +24,4 @@ jobs: - name: Install modules run: npm i - name: Run Cypress - run: npm run test \ No newline at end of file + run: NEXT_PUBLIC_TEST_ENV=test npm run test \ No newline at end of file diff --git a/cypress/integration/bridgeapi/users/login.spec.js b/cypress/integration/bridgeapi/users/login.spec.js index 78434cf..0721160 100644 --- a/cypress/integration/bridgeapi/users/login.spec.js +++ b/cypress/integration/bridgeapi/users/login.spec.js @@ -46,19 +46,19 @@ describe('Login', () => { // TODO: Test snackbar // cy.get('.MuiAlert-message').contains('Account has been created. Redirecting...'); - cy.location({ timeout: 10000 }).should((location) => { + cy.location().should((location) => { expect(location.pathname).to.eq('/dashboard'); }); }); - // it('can handle failed login', () => { - // stubFailLogin(); - // inputFields(); - // submit(); + it('can handle failed login', () => { + stubFailLogin(); + inputFields(); + submit(); - // // cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); - // cy.location().should((location) => { - // expect(location.pathname).to.eq('/users/login'); - // }); - // }); + // cy.get('.MuiAlert-message').contains('Some error occurred. Please try again.'); + cy.location().should((location) => { + expect(location.pathname).to.eq('/users/login'); + }); + }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 81f8954..ef590eb 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,12 +23,9 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -Cypress.Commands.add('stubRequest', (url, method, status, response) => { - cy.server(); - cy.route({ - url, - method, - status, - response, +Cypress.Commands.add('stubRequest', (endpoint, method, statusCode, body) => { + cy.intercept(method, endpoint, { + statusCode, + body, }); }); \ No newline at end of file diff --git a/pages/users/login.js b/pages/users/login.js index faa8f58..d922608 100644 --- a/pages/users/login.js +++ b/pages/users/login.js @@ -81,7 +81,6 @@ function Login() { // protection. // // window.location causes a full refresh which solves the issue. - debugger; window.location.pathname = '/dashboard'; } else { setFormMessage('Error: Email or password is invalid'); diff --git a/utils/api.js b/utils/api.js index bb4b7cd..e463eb2 100644 --- a/utils/api.js +++ b/utils/api.js @@ -1,12 +1,22 @@ import Axios from 'axios'; const urls = { - test: 'http://localhost:3001', development: 'http://localhost:3001/', - production: 'http://localhost:3001/', + production: 'http://localhost:3004/', }; + +// Tests require the application to be built and +// ran with `npm run start` which ALWAYS sets the +// NODE_ENV to `production`. NEXT_PUBLIC_TEST_ENV +// is designed to be used in a test environment. +// This ternary was a solution for tests without +// making breaking changes to dev/production behavior. +const url = process.env.NEXT_PUBLIC_TEST_ENV + ? '' + : urls[process.env.NODE_ENV]; + const api = Axios.create({ - baseURL: urls[process.env.NODE_ENV], + baseURL: url, headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 205a4804542887e8ff00090576acc1c13a1c1bf8 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 26 Nov 2020 08:47:21 -0500 Subject: [PATCH 06/12] Trying to get intercept to work with getServerSideProps --- cypress/integration/bridgeapi/users/login.spec.js | 1 + pages/dashboard.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/bridgeapi/users/login.spec.js b/cypress/integration/bridgeapi/users/login.spec.js index 0721160..e8005b7 100644 --- a/cypress/integration/bridgeapi/users/login.spec.js +++ b/cypress/integration/bridgeapi/users/login.spec.js @@ -19,6 +19,7 @@ const stubDashboard = () => { bridges: [], }; cy.stubRequest('/bridges', 'GET', 200, response); + // cy.intercept('/bbb', response); }; const inputFields = () => { diff --git a/pages/dashboard.js b/pages/dashboard.js index 8bb468e..3ad3e53 100644 --- a/pages/dashboard.js +++ b/pages/dashboard.js @@ -58,7 +58,7 @@ export default Dashboard; export async function getServerSideProps(context) { const res = await fetchDataOrRedirect(context, '/bridges'); - console.log(res); + if (!res) return { props: {} }; // Redirecting to /users/login return { From f50549e049bc259f522530cc07dc4c534dc41cd4 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 26 Nov 2020 09:27:07 -0500 Subject: [PATCH 07/12] Move specs to specs folder, configure dynamic msw stubs --- cypress.json | 8 ++++- cypress/videos/bridgeapi/sign_up.spec.js.mp4 | Bin 31061 -> 0 bytes cypress/videos/examples/actions.spec.js.mp4 | Bin 262 -> 0 bytes mocks/handlers.js | 9 ----- pages/_app.js | 4 +-- {cypress => specs}/fixtures/example.json | 0 {cypress => specs}/fixtures/user.json | 0 .../integration/bridgeapi/users/login.spec.js | 0 .../bridgeapi/users/sign_up.spec.js | 0 .../integration/examples/actions.spec.js | 0 .../integration/examples/aliasing.spec.js | 0 .../integration/examples/assertions.spec.js | 0 .../integration/examples/connectors.spec.js | 0 .../integration/examples/cookies.spec.js | 0 .../integration/examples/cypress_api.spec.js | 0 .../integration/examples/files.spec.js | 0 .../examples/local_storage.spec.js | 0 .../integration/examples/location.spec.js | 0 .../integration/examples/misc.spec.js | 0 .../integration/examples/navigation.spec.js | 0 .../examples/network_requests.spec.js | 0 .../integration/examples/querying.spec.js | 0 .../examples/spies_stubs_clocks.spec.js | 0 .../integration/examples/traversal.spec.js | 0 .../integration/examples/utilities.spec.js | 0 .../integration/examples/viewport.spec.js | 0 .../integration/examples/waiting.spec.js | 0 .../integration/examples/window.spec.js | 0 {cypress => specs}/plugins/index.js | 0 {cypress => specs}/support/commands.js | 10 +++++- {cypress => specs}/support/index.js | 0 {mocks => specs/support/mocks}/browser.js | 0 specs/support/mocks/handlers.js | 31 ++++++++++++++++++ {mocks => specs/support/mocks}/index.js | 0 {mocks => specs/support/mocks}/server.js | 0 35 files changed, 49 insertions(+), 13 deletions(-) delete mode 100644 cypress/videos/bridgeapi/sign_up.spec.js.mp4 delete mode 100644 cypress/videos/examples/actions.spec.js.mp4 delete mode 100644 mocks/handlers.js rename {cypress => specs}/fixtures/example.json (100%) rename {cypress => specs}/fixtures/user.json (100%) rename {cypress => specs}/integration/bridgeapi/users/login.spec.js (100%) rename {cypress => specs}/integration/bridgeapi/users/sign_up.spec.js (100%) rename {cypress => specs}/integration/examples/actions.spec.js (100%) rename {cypress => specs}/integration/examples/aliasing.spec.js (100%) rename {cypress => specs}/integration/examples/assertions.spec.js (100%) rename {cypress => specs}/integration/examples/connectors.spec.js (100%) rename {cypress => specs}/integration/examples/cookies.spec.js (100%) rename {cypress => specs}/integration/examples/cypress_api.spec.js (100%) rename {cypress => specs}/integration/examples/files.spec.js (100%) rename {cypress => specs}/integration/examples/local_storage.spec.js (100%) rename {cypress => specs}/integration/examples/location.spec.js (100%) rename {cypress => specs}/integration/examples/misc.spec.js (100%) rename {cypress => specs}/integration/examples/navigation.spec.js (100%) rename {cypress => specs}/integration/examples/network_requests.spec.js (100%) rename {cypress => specs}/integration/examples/querying.spec.js (100%) rename {cypress => specs}/integration/examples/spies_stubs_clocks.spec.js (100%) rename {cypress => specs}/integration/examples/traversal.spec.js (100%) rename {cypress => specs}/integration/examples/utilities.spec.js (100%) rename {cypress => specs}/integration/examples/viewport.spec.js (100%) rename {cypress => specs}/integration/examples/waiting.spec.js (100%) rename {cypress => specs}/integration/examples/window.spec.js (100%) rename {cypress => specs}/plugins/index.js (100%) rename {cypress => specs}/support/commands.js (84%) rename {cypress => specs}/support/index.js (100%) rename {mocks => specs/support/mocks}/browser.js (100%) create mode 100644 specs/support/mocks/handlers.js rename {mocks => specs/support/mocks}/index.js (100%) rename {mocks => specs/support/mocks}/server.js (100%) diff --git a/cypress.json b/cypress.json index 1a27757..807b8c6 100644 --- a/cypress.json +++ b/cypress.json @@ -1,3 +1,9 @@ { - "baseUrl": "http://localhost:3000" + "baseUrl": "http://localhost:3000", + "fixturesFolder": "specs/fixtures", + "integrationFolder": "specs/integration", + "pluginsFile": "specs/plugins/index.js", + "screenshotsFolder": "specs/screenshots", + "videosFolder": "specs/videos", + "supportFile": "specs/support/index.js" } \ No newline at end of file diff --git a/cypress/videos/bridgeapi/sign_up.spec.js.mp4 b/cypress/videos/bridgeapi/sign_up.spec.js.mp4 deleted file mode 100644 index 861d894567cdc6bd63dacf1f98123d1a501d222e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31061 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 diff --git a/cypress/videos/examples/actions.spec.js.mp4 b/cypress/videos/examples/actions.spec.js.mp4 deleted file mode 100644 index 61ac81f706995b35682fd72056d40b36ffbfa657..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmZQzU{FXasVvAW&d+6FU}6B#Kx~v)mTZ_?U}DI?z`&7Kl$r{nb5jyafb_N8{QNQ? zos(OZkpiTV0P_nlhmnB+h!6mU0~AK%J0MhIV=(~*lS)%c5`lD7ZYr1tsZ-2I$teOc zKp;0Ivna8kAP2&Okh+;U#UKZ(t}MyV2hy@Y_k#=pTkn%tmS$?9XJn#hXkY*U4q_zw diff --git a/mocks/handlers.js b/mocks/handlers.js deleted file mode 100644 index 2781f31..0000000 --- a/mocks/handlers.js +++ /dev/null @@ -1,9 +0,0 @@ -import { rest } from 'msw'; -// import users from 'data/users'; // contains mock data for users -// import messages from 'data/messages';// contains mock data for messages - -const handlers = [ - rest.get('http://localhost:3001/bridges', (req, res, ctx) => res(ctx.json([]))), -]; - -export default handlers; diff --git a/pages/_app.js b/pages/_app.js index fa3cb4f..25086dc 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -12,9 +12,9 @@ import AuthProvider from '../src/contexts/auth'; // If TEST_ENV is truthy, include mocks in the build. // This is strictly for testing. Can't do NODE_ENV as // `next build` overwrites NODE_ENV with production. -if (process.env.TEST_ENV) { +if (process.env.NEXT_PUBLIC_TEST_ENV) { // eslint-disable-next-line global-require - require('../mocks'); + require('../specs/support/mocks'); } export default function MyApp(props) { diff --git a/cypress/fixtures/example.json b/specs/fixtures/example.json similarity index 100% rename from cypress/fixtures/example.json rename to specs/fixtures/example.json diff --git a/cypress/fixtures/user.json b/specs/fixtures/user.json similarity index 100% rename from cypress/fixtures/user.json rename to specs/fixtures/user.json diff --git a/cypress/integration/bridgeapi/users/login.spec.js b/specs/integration/bridgeapi/users/login.spec.js similarity index 100% rename from cypress/integration/bridgeapi/users/login.spec.js rename to specs/integration/bridgeapi/users/login.spec.js diff --git a/cypress/integration/bridgeapi/users/sign_up.spec.js b/specs/integration/bridgeapi/users/sign_up.spec.js similarity index 100% rename from cypress/integration/bridgeapi/users/sign_up.spec.js rename to specs/integration/bridgeapi/users/sign_up.spec.js diff --git a/cypress/integration/examples/actions.spec.js b/specs/integration/examples/actions.spec.js similarity index 100% rename from cypress/integration/examples/actions.spec.js rename to specs/integration/examples/actions.spec.js diff --git a/cypress/integration/examples/aliasing.spec.js b/specs/integration/examples/aliasing.spec.js similarity index 100% rename from cypress/integration/examples/aliasing.spec.js rename to specs/integration/examples/aliasing.spec.js diff --git a/cypress/integration/examples/assertions.spec.js b/specs/integration/examples/assertions.spec.js similarity index 100% rename from cypress/integration/examples/assertions.spec.js rename to specs/integration/examples/assertions.spec.js diff --git a/cypress/integration/examples/connectors.spec.js b/specs/integration/examples/connectors.spec.js similarity index 100% rename from cypress/integration/examples/connectors.spec.js rename to specs/integration/examples/connectors.spec.js diff --git a/cypress/integration/examples/cookies.spec.js b/specs/integration/examples/cookies.spec.js similarity index 100% rename from cypress/integration/examples/cookies.spec.js rename to specs/integration/examples/cookies.spec.js diff --git a/cypress/integration/examples/cypress_api.spec.js b/specs/integration/examples/cypress_api.spec.js similarity index 100% rename from cypress/integration/examples/cypress_api.spec.js rename to specs/integration/examples/cypress_api.spec.js diff --git a/cypress/integration/examples/files.spec.js b/specs/integration/examples/files.spec.js similarity index 100% rename from cypress/integration/examples/files.spec.js rename to specs/integration/examples/files.spec.js diff --git a/cypress/integration/examples/local_storage.spec.js b/specs/integration/examples/local_storage.spec.js similarity index 100% rename from cypress/integration/examples/local_storage.spec.js rename to specs/integration/examples/local_storage.spec.js diff --git a/cypress/integration/examples/location.spec.js b/specs/integration/examples/location.spec.js similarity index 100% rename from cypress/integration/examples/location.spec.js rename to specs/integration/examples/location.spec.js diff --git a/cypress/integration/examples/misc.spec.js b/specs/integration/examples/misc.spec.js similarity index 100% rename from cypress/integration/examples/misc.spec.js rename to specs/integration/examples/misc.spec.js diff --git a/cypress/integration/examples/navigation.spec.js b/specs/integration/examples/navigation.spec.js similarity index 100% rename from cypress/integration/examples/navigation.spec.js rename to specs/integration/examples/navigation.spec.js diff --git a/cypress/integration/examples/network_requests.spec.js b/specs/integration/examples/network_requests.spec.js similarity index 100% rename from cypress/integration/examples/network_requests.spec.js rename to specs/integration/examples/network_requests.spec.js diff --git a/cypress/integration/examples/querying.spec.js b/specs/integration/examples/querying.spec.js similarity index 100% rename from cypress/integration/examples/querying.spec.js rename to specs/integration/examples/querying.spec.js diff --git a/cypress/integration/examples/spies_stubs_clocks.spec.js b/specs/integration/examples/spies_stubs_clocks.spec.js similarity index 100% rename from cypress/integration/examples/spies_stubs_clocks.spec.js rename to specs/integration/examples/spies_stubs_clocks.spec.js diff --git a/cypress/integration/examples/traversal.spec.js b/specs/integration/examples/traversal.spec.js similarity index 100% rename from cypress/integration/examples/traversal.spec.js rename to specs/integration/examples/traversal.spec.js diff --git a/cypress/integration/examples/utilities.spec.js b/specs/integration/examples/utilities.spec.js similarity index 100% rename from cypress/integration/examples/utilities.spec.js rename to specs/integration/examples/utilities.spec.js diff --git a/cypress/integration/examples/viewport.spec.js b/specs/integration/examples/viewport.spec.js similarity index 100% rename from cypress/integration/examples/viewport.spec.js rename to specs/integration/examples/viewport.spec.js diff --git a/cypress/integration/examples/waiting.spec.js b/specs/integration/examples/waiting.spec.js similarity index 100% rename from cypress/integration/examples/waiting.spec.js rename to specs/integration/examples/waiting.spec.js diff --git a/cypress/integration/examples/window.spec.js b/specs/integration/examples/window.spec.js similarity index 100% rename from cypress/integration/examples/window.spec.js rename to specs/integration/examples/window.spec.js diff --git a/cypress/plugins/index.js b/specs/plugins/index.js similarity index 100% rename from cypress/plugins/index.js rename to specs/plugins/index.js diff --git a/cypress/support/commands.js b/specs/support/commands.js similarity index 84% rename from cypress/support/commands.js rename to specs/support/commands.js index ef590eb..5d0246c 100644 --- a/cypress/support/commands.js +++ b/specs/support/commands.js @@ -28,4 +28,12 @@ Cypress.Commands.add('stubRequest', (endpoint, method, statusCode, body) => { statusCode, body, }); -}); \ No newline at end of file +}); + +Cypress.Commands.add('setToken', () => { + cy.setCookie('token', 'goodToken'); +}); + +Cypress.Commands.add('setBadToken', () => { + cy.setCookie('token', 'badToken'); +}); diff --git a/cypress/support/index.js b/specs/support/index.js similarity index 100% rename from cypress/support/index.js rename to specs/support/index.js diff --git a/mocks/browser.js b/specs/support/mocks/browser.js similarity index 100% rename from mocks/browser.js rename to specs/support/mocks/browser.js diff --git a/specs/support/mocks/handlers.js b/specs/support/mocks/handlers.js new file mode 100644 index 0000000..1a1e8c2 --- /dev/null +++ b/specs/support/mocks/handlers.js @@ -0,0 +1,31 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { rest } from 'msw'; + +// msw doesn't give us a way to stub requests on a per spec basis. Because +// of this, we need to make our own way. Use `cy.setToken` to create a +// token that will be valid. Use `cy.setBadToken` to create an invalid token. +// Note: You will not be able to use cy.visit in a `beforeEach` if you want +// to use both valid & invalid tokens in a test suite as you must set cookies +// prior to visiting. +const invalidToken = (req) => req.headers.map['bridge-jwt'] === 'badToken'; + +const handlers = [ + rest.get('http://localhost/bridges', (req, res, ctx) => { + if (invalidToken(req)) { + return res( + ctx.status(401), + ctx.json( + {}, + ), + ); + } + + return res( + ctx.json( + { bridges: [] }, + ), + ); + }), +]; + +export default handlers; diff --git a/mocks/index.js b/specs/support/mocks/index.js similarity index 100% rename from mocks/index.js rename to specs/support/mocks/index.js diff --git a/mocks/server.js b/specs/support/mocks/server.js similarity index 100% rename from mocks/server.js rename to specs/support/mocks/server.js From 5602a54b19b10a564fa783968feef8c7bb53fb1f Mon Sep 17 00:00:00 2001 From: = Date: Thu, 26 Nov 2020 10:10:42 -0500 Subject: [PATCH 08/12] DRY loging & sign_up specs --- .../integration/bridgeapi/users/login.spec.js | 79 +++++++------ .../bridgeapi/users/sign_up.spec.js | 109 ++++++++++-------- specs/support/index.js | 2 +- specs/support/utils/login_signup_forms.js | 66 +++++++++++ 4 files changed, 174 insertions(+), 82 deletions(-) create mode 100644 specs/support/utils/login_signup_forms.js diff --git a/specs/integration/bridgeapi/users/login.spec.js b/specs/integration/bridgeapi/users/login.spec.js index e8005b7..0e682a2 100644 --- a/specs/integration/bridgeapi/users/login.spec.js +++ b/specs/integration/bridgeapi/users/login.spec.js @@ -1,47 +1,25 @@ /// -const stubSuccessLogin = () => { - const response = { - token: '123984790182347', - }; - cy.stubRequest('/login', 'POST', 201, response); -}; - -const stubFailLogin = () => { - const response = { - token: '123984790182347', - }; - cy.stubRequest('/login', 'POST', 422, response); -}; - -const stubDashboard = () => { - const response = { - bridges: [], - }; - cy.stubRequest('/bridges', 'GET', 200, response); - // cy.intercept('/bbb', response); -}; - -const inputFields = () => { - cy.get('#email-input') - .type('demo@demo.com').should('have.value', 'demo@demo.com'); - - cy.get('#password-input') - .type('password').should('have.value', 'password'); -}; - -const submit = () => { - cy.get('form').submit(); -}; +import { + stubSuccessLogin, + stubFailLogin, + inputEmail, + inputPassword, + inputLoginFields as inputFields, + submit, +} from '../../../support/utils/login_signup_forms'; describe('Login', () => { beforeEach(() => { cy.visit('/users/login'); }); + afterEach(() => { + cy.clearCookies(); + }); + it('can login', () => { stubSuccessLogin(); - stubDashboard(); inputFields(); submit(); @@ -52,7 +30,7 @@ describe('Login', () => { }); }); - it('can handle failed login', () => { + it('can handle failed api request', () => { stubFailLogin(); inputFields(); submit(); @@ -62,4 +40,35 @@ describe('Login', () => { expect(location.pathname).to.eq('/users/login'); }); }); + + it('is invalid without email', () => { + inputPassword(); + submit(); + + cy.get('#email-input').parent().should('have.class', 'Mui-error'); + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Required').should('be.visible'); + }); + + it('is invalid wtih bad email', () => { + inputEmail('demo@demo'); + inputPassword(); + + submit(); + + cy.get('#email-input').parent().should('have.class', 'Mui-error'); + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Invalid Email Address').should('be.visible'); + }); + + it('is invalid without password', () => { + inputEmail(); + submit(); + + cy.get('#password-input') + .parent() + .should('have.class', 'Mui-error'); + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Required').should('be.visible'); + }); }); diff --git a/specs/integration/bridgeapi/users/sign_up.spec.js b/specs/integration/bridgeapi/users/sign_up.spec.js index ba78504..961a522 100644 --- a/specs/integration/bridgeapi/users/sign_up.spec.js +++ b/specs/integration/bridgeapi/users/sign_up.spec.js @@ -1,56 +1,26 @@ /// -const stubSuccessSignUp = () => { - const response = { - user: { - email: 'demo@demo.com', - notifications: false, - }, - }; - cy.stubRequest('/user', 'POST', 201, response); -}; - -const stubFailSignUp = () => { - const response = { - error: 'email or password is invalid', - }; - cy.stubRequest('/user', 'POST', 422, response); -}; - -const stubSuccessLogin = () => { - const response = { - token: '123984790182347', - }; - cy.stubRequest('/login', 'POST', 201, response); -}; - -const stubFailLogin = () => { - const response = { - token: '123984790182347', - }; - cy.stubRequest('/login', 'POST', 422, response); -}; - -const inputFields = () => { - cy.get('#email-input') - .type('demo@demo.com').should('have.value', 'demo@demo.com'); - - cy.get('#password-input') - .type('password').should('have.value', 'password'); - - cy.get('#password-confirmation-input') - .type('password').should('have.value', 'password'); -}; - -const submit = () => { - cy.get('form').submit(); -}; +import { + stubSuccessSignUp, + stubFailSignUp, + stubSuccessLogin, + stubFailLogin, + inputEmail, + inputPassword, + inputPasswordConfirmation, + inputSignUpFields as inputFields, + submit, +} from '../../../support/utils/login_signup_forms'; describe('Sign Up', () => { beforeEach(() => { cy.visit('/users/signup'); }); + afterEach(() => { + cy.clearCookies(); + }); + it('can sign up & login', () => { stubSuccessSignUp(); stubSuccessLogin(); @@ -74,7 +44,7 @@ describe('Sign Up', () => { }); }); - it('can handle failed login', () => { + it('can handle failed api request', () => { stubSuccessSignUp(); stubFailLogin(); inputFields(); @@ -84,4 +54,51 @@ describe('Sign Up', () => { expect(location.pathname).to.eq('/users/login'); }); }); + + it('is invalid without email', () => { + inputPassword(); + inputPasswordConfirmation(); + + submit(); + + cy.get('#email-input').parent().should('have.class', 'Mui-error'); + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Required').should('be.visible'); + }); + + it('is invalid without password', () => { + inputEmail(); + inputPasswordConfirmation(); + + submit(); + + cy.get('#password-input').parent().should('have.class', 'Mui-error'); + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Required').should('be.visible'); + }); + + it('is invalid without password confirmation', () => { + inputEmail(); + inputPassword(); + + submit(); + + cy.get('#password-confirmation-input').parent().should('have.class', 'Mui-error'); + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Required').should('be.visible'); + }); + + it('is invalid when passwords don\'t match', () => { + inputEmail(); + inputPassword(); + inputPasswordConfirmation('fakeword'); + + submit(); + + cy.get('#password-input').parent().should('have.class', 'Mui-error'); + cy.get('#password-confirmation-input').parent().should('have.class', 'Mui-error'); + + cy.get('.MuiFormHelperText-root.MuiFormHelperText-contained.Mui-error') + .contains('Passwords do not match').should('be.visible'); + }); }); diff --git a/specs/support/index.js b/specs/support/index.js index d68db96..37a498f 100644 --- a/specs/support/index.js +++ b/specs/support/index.js @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/specs/support/utils/login_signup_forms.js b/specs/support/utils/login_signup_forms.js new file mode 100644 index 0000000..d265c9a --- /dev/null +++ b/specs/support/utils/login_signup_forms.js @@ -0,0 +1,66 @@ +export const stubSuccessSignUp = () => { + const response = { + user: { + email: 'demo@demo.com', + notifications: false, + }, + }; + cy.stubRequest('/user', 'POST', 201, response); +}; + +export const stubFailSignUp = () => { + const response = { + error: 'email or password is invalid', + }; + cy.stubRequest('/user', 'POST', 422, response); +}; + +export const stubSuccessLogin = () => { + const response = { + token: '123984790182347', + }; + cy.stubRequest('/login', 'POST', 201, response); +}; + +export const stubFailLogin = () => { + const response = { + token: '123984790182347', + }; + cy.stubRequest('/login', 'POST', 422, response); +}; + +export const inputEmail = (email) => { + const input = email || 'demo@demo.com'; + + cy.get('#email-input') + .type(input).should('have.value', input); +}; + +export const inputPassword = (pw) => { + const input = pw || 'password'; + + cy.get('#password-input') + .type(input).should('have.value', input); +}; + +export const inputPasswordConfirmation = (pwc) => { + const input = pwc || 'password'; + + cy.get('#password-confirmation-input') + .type(input).should('have.value', input); +}; + +export const inputSignUpFields = (email, pw, pwc) => { + inputEmail(email); + inputPassword(pw); + inputPasswordConfirmation(pwc); +}; + +export const inputLoginFields = (email, pw) => { + inputEmail(email); + inputPassword(pw); +}; + +export const submit = () => { + cy.get('form').submit(); +}; From e6c35e8bd23fd1b89a653eb168d2a035d154b0fc Mon Sep 17 00:00:00 2001 From: = Date: Thu, 26 Nov 2020 10:20:02 -0500 Subject: [PATCH 09/12] Remove cypress examples --- specs/integration/examples/actions.spec.js | 299 ------------------ specs/integration/examples/aliasing.spec.js | 40 --- specs/integration/examples/assertions.spec.js | 177 ----------- specs/integration/examples/connectors.spec.js | 97 ------ specs/integration/examples/cookies.spec.js | 77 ----- .../integration/examples/cypress_api.spec.js | 222 ------------- specs/integration/examples/files.spec.js | 115 ------- .../examples/local_storage.spec.js | 52 --- specs/integration/examples/location.spec.js | 32 -- specs/integration/examples/misc.spec.js | 104 ------ specs/integration/examples/navigation.spec.js | 56 ---- .../examples/network_requests.spec.js | 205 ------------ specs/integration/examples/querying.spec.js | 114 ------- .../examples/spies_stubs_clocks.spec.js | 205 ------------ specs/integration/examples/traversal.spec.js | 121 ------- specs/integration/examples/utilities.spec.js | 136 -------- specs/integration/examples/viewport.spec.js | 59 ---- specs/integration/examples/waiting.spec.js | 33 -- specs/integration/examples/window.spec.js | 22 -- 19 files changed, 2166 deletions(-) delete mode 100644 specs/integration/examples/actions.spec.js delete mode 100644 specs/integration/examples/aliasing.spec.js delete mode 100644 specs/integration/examples/assertions.spec.js delete mode 100644 specs/integration/examples/connectors.spec.js delete mode 100644 specs/integration/examples/cookies.spec.js delete mode 100644 specs/integration/examples/cypress_api.spec.js delete mode 100644 specs/integration/examples/files.spec.js delete mode 100644 specs/integration/examples/local_storage.spec.js delete mode 100644 specs/integration/examples/location.spec.js delete mode 100644 specs/integration/examples/misc.spec.js delete mode 100644 specs/integration/examples/navigation.spec.js delete mode 100644 specs/integration/examples/network_requests.spec.js delete mode 100644 specs/integration/examples/querying.spec.js delete mode 100644 specs/integration/examples/spies_stubs_clocks.spec.js delete mode 100644 specs/integration/examples/traversal.spec.js delete mode 100644 specs/integration/examples/utilities.spec.js delete mode 100644 specs/integration/examples/viewport.spec.js delete mode 100644 specs/integration/examples/waiting.spec.js delete mode 100644 specs/integration/examples/window.spec.js diff --git a/specs/integration/examples/actions.spec.js b/specs/integration/examples/actions.spec.js deleted file mode 100644 index ef430ed..0000000 --- a/specs/integration/examples/actions.spec.js +++ /dev/null @@ -1,299 +0,0 @@ -/// - -context('Actions', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/actions') - }) - - // https://on.cypress.io/interacting-with-elements - - it('.type() - type into a DOM element', () => { - // https://on.cypress.io/type - cy.get('.action-email') - .type('fake@email.com').should('have.value', 'fake@email.com') - - // .type() with special character sequences - .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') - .type('{del}{selectall}{backspace}') - - // .type() with key modifiers - .type('{alt}{option}') //these are equivalent - .type('{ctrl}{control}') //these are equivalent - .type('{meta}{command}{cmd}') //these are equivalent - .type('{shift}') - - // Delay each keypress by 0.1 sec - .type('slow.typing@email.com', { delay: 100 }) - .should('have.value', 'slow.typing@email.com') - - cy.get('.action-disabled') - // Ignore error checking prior to type - // like whether the input is visible or disabled - .type('disabled error checking', { force: true }) - .should('have.value', 'disabled error checking') - }) - - it('.focus() - focus on a DOM element', () => { - // https://on.cypress.io/focus - cy.get('.action-focus').focus() - .should('have.class', 'focus') - .prev().should('have.attr', 'style', 'color: orange;') - }) - - it('.blur() - blur off a DOM element', () => { - // https://on.cypress.io/blur - cy.get('.action-blur').type('About to blur').blur() - .should('have.class', 'error') - .prev().should('have.attr', 'style', 'color: red;') - }) - - it('.clear() - clears an input or textarea element', () => { - // https://on.cypress.io/clear - cy.get('.action-clear').type('Clear this text') - .should('have.value', 'Clear this text') - .clear() - .should('have.value', '') - }) - - it('.submit() - submit a form', () => { - // https://on.cypress.io/submit - cy.get('.action-form') - .find('[type="text"]').type('HALFOFF') - - cy.get('.action-form').submit() - .next().should('contain', 'Your form has been submitted!') - }) - - it('.click() - click on a DOM element', () => { - // https://on.cypress.io/click - cy.get('.action-btn').click() - - // You can click on 9 specific positions of an element: - // ----------------------------------- - // | topLeft top topRight | - // | | - // | | - // | | - // | left center right | - // | | - // | | - // | | - // | bottomLeft bottom bottomRight | - // ----------------------------------- - - // clicking in the center of the element is the default - cy.get('#action-canvas').click() - - cy.get('#action-canvas').click('topLeft') - cy.get('#action-canvas').click('top') - cy.get('#action-canvas').click('topRight') - cy.get('#action-canvas').click('left') - cy.get('#action-canvas').click('right') - cy.get('#action-canvas').click('bottomLeft') - cy.get('#action-canvas').click('bottom') - cy.get('#action-canvas').click('bottomRight') - - // .click() accepts an x and y coordinate - // that controls where the click occurs :) - - cy.get('#action-canvas') - .click(80, 75) // click 80px on x coord and 75px on y coord - .click(170, 75) - .click(80, 165) - .click(100, 185) - .click(125, 190) - .click(150, 185) - .click(170, 165) - - // click multiple elements by passing multiple: true - cy.get('.action-labels>.label').click({ multiple: true }) - - // Ignore error checking prior to clicking - cy.get('.action-opacity>.btn').click({ force: true }) - }) - - it('.dblclick() - double click on a DOM element', () => { - // https://on.cypress.io/dblclick - - // Our app has a listener on 'dblclick' event in our 'scripts.js' - // that hides the div and shows an input on double click - cy.get('.action-div').dblclick().should('not.be.visible') - cy.get('.action-input-hidden').should('be.visible') - }) - - it('.rightclick() - right click on a DOM element', () => { - // https://on.cypress.io/rightclick - - // Our app has a listener on 'contextmenu' event in our 'scripts.js' - // that hides the div and shows an input on right click - cy.get('.rightclick-action-div').rightclick().should('not.be.visible') - cy.get('.rightclick-action-input-hidden').should('be.visible') - }) - - it('.check() - check a checkbox or radio element', () => { - // https://on.cypress.io/check - - // By default, .check() will check all - // matching checkbox or radio elements in succession, one after another - cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') - .check().should('be.checked') - - cy.get('.action-radios [type="radio"]').not('[disabled]') - .check().should('be.checked') - - // .check() accepts a value argument - cy.get('.action-radios [type="radio"]') - .check('radio1').should('be.checked') - - // .check() accepts an array of values - cy.get('.action-multiple-checkboxes [type="checkbox"]') - .check(['checkbox1', 'checkbox2']).should('be.checked') - - // Ignore error checking prior to checking - cy.get('.action-checkboxes [disabled]') - .check({ force: true }).should('be.checked') - - cy.get('.action-radios [type="radio"]') - .check('radio3', { force: true }).should('be.checked') - }) - - it('.uncheck() - uncheck a checkbox element', () => { - // https://on.cypress.io/uncheck - - // By default, .uncheck() will uncheck all matching - // checkbox elements in succession, one after another - cy.get('.action-check [type="checkbox"]') - .not('[disabled]') - .uncheck().should('not.be.checked') - - // .uncheck() accepts a value argument - cy.get('.action-check [type="checkbox"]') - .check('checkbox1') - .uncheck('checkbox1').should('not.be.checked') - - // .uncheck() accepts an array of values - cy.get('.action-check [type="checkbox"]') - .check(['checkbox1', 'checkbox3']) - .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') - - // Ignore error checking prior to unchecking - cy.get('.action-check [disabled]') - .uncheck({ force: true }).should('not.be.checked') - }) - - it('.select() - select an option in a