diff --git a/cypress.config.js b/cypress.config.js index 529841c3..93a4fccd 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -23,12 +23,14 @@ module.exports = defineConfig({ env: { TEST_SCIENTIST_USER: 'test@test.com', TEST_SCIENTIST_PW: '!test1234', - NEXT_PUBLIC_PROVIDER_NAME: 'acme', - NEXT_PUBLIC_PROVIDER_ID: '572' + NEXT_PUBLIC_PROVIDER_NAME: process.env.NEXT_PUBLIC_PROVIDER_NAME, + NEXT_PUBLIC_PROVIDER_ID: process.env.NEXT_PUBLIC_PROVIDER_ID, + NEXT_PUBLIC_TOKEN: process.env.NEXT_PUBLIC_TOKEN, + CYPRESS_SEARCH_QUERY: 'test', }, reporter: 'junit', reporterOptions: { mochaFile: 'cypress/results/results-[hash].xml', toConsole: true, }, -}); +}) diff --git a/cypress/e2e/browse.cy.js b/cypress/e2e/browse.cy.js index dd0e64f0..aee53206 100644 --- a/cypress/e2e/browse.cy.js +++ b/cypress/e2e/browse.cy.js @@ -19,10 +19,10 @@ describe('Browsing', () => { emptyFixture: 'services/no-wares.json', }, ] - + beforeEach(() => { // Intercept the responses from the endpoint to view all requests. - // Even though this is to the same endpoint, the call happens on each page twice, + // Even though this is to the same endpoint, the call happens on each page twice, // once when the page loads with all the wares, and again after any search is performed. // this makes it necessary to create an intercept for each time the call is made. intercepts.forEach((intercept) => { @@ -103,39 +103,4 @@ describe('Browsing', () => { }) }) }) - - describe('from the home page', () => { - beforeEach(() => { - wares = true - // Intercept the api call being made on the homepage - cy.customApiIntercept({ - action: 'GET', - alias: 'useAllWares', - requestURL: `/providers/${Cypress.env('NEXT_PUBLIC_PROVIDER_ID')}/wares.json`, - data: wares, - defaultFixture: 'services/wares.json', - loading, - error - }) - cy.visit('/') - }) - - context('a search is completed successfully and', () => { - it('navigates to "/browse" with a blank query', () => { - cy.get('button.search-button').click() - cy.url().should('include', '/browse') - cy.url().should('not.include', '?') - cy.get('input.search-bar').should('have.value', '') - cy.get(".card[data-cy='item-card']").should('be.visible') - }) - - it('navigates to "/browse" with a query term', () => { - cy.get('input.search-bar').type('test') - cy.get('button.search-button').click() - cy.url().should('include', '/browse?q=test') - cy.get('input.search-bar').should('have.value', 'test') - cy.get(".card[data-cy='item-card']").should('be.visible') - }) - }) - }) -}) \ No newline at end of file +}) diff --git a/cypress/e2e/home.cy.js b/cypress/e2e/home.cy.js index cff47a0a..5bc8e0f9 100644 --- a/cypress/e2e/home.cy.js +++ b/cypress/e2e/home.cy.js @@ -1,67 +1,120 @@ -describe('Viewing Home page', () => { +describe('Navigating to the home page', () => { // declare variables that can be used to change how the response is intercepted. - let loading + let data = 'services/wares.json' let error - let featuredServices beforeEach(() => { - // Intercept the response from the endpoint to view all requests cy.customApiIntercept({ - action: 'GET', alias: 'useAllWares', - requestURL: `/providers/${Cypress.env('NEXT_PUBLIC_PROVIDER_ID')}/wares.json`, - data: featuredServices, - defaultFixture: 'services/wares.json', - emptyFixture: 'services/no-wares.json', - loading, - error + data, + error, + requestURL: '/wares.json?per_page=2000', }) + cy.visit('/') }) - - context('featured services list is loading', () => { - before(() => { - loading = true + describe('renders a search bar', () => { + it('with no query', () => { + cy.get("form[data-cy='search-bar']").should('exist').then(() => { + cy.log('Search bar renders successfully.') + }) }) - it('should show 3 placeholder cards loading', () => { - cy.get('p.placeholder-glow').should('be.visible').then(() => { - cy.log('Loading text displays correctly.') + + context('able to navigate to "/browse"', () => { + const testSetup = ({ data, defaultFixture, requestURL }) => { + cy.customApiIntercept({ + alias: 'useFilteredWares', + data, + error, + requestURL, + }) + } + + it('with a blank query', () => { + testSetup({ + data: 'services/wares.json', + requestURL: '/wares.json?per_page=2000&q=', + }) + + cy.get('button.search-button').click() + cy.url().should('include', '/browse') + cy.url().should('not.include', '?') + cy.get('input.search-bar').should('have.value', '') + cy.get(".card[data-cy='item-card']").should('be.visible') + }) + + it('with a valid query term', () => { + testSetup({ + data: 'services/filtered-wares.json', + requestURL: `/wares.json?per_page=2000&q=${Cypress.env('CYPRESS_SEARCH_QUERY')}`, + }) + + cy.get('input.search-bar').type(Cypress.env('CYPRESS_SEARCH_QUERY')) + cy.get('button.search-button').click() + cy.url().should('include', `/browse?q=${Cypress.env('CYPRESS_SEARCH_QUERY')}`) + cy.get('input.search-bar').should('have.value', Cypress.env('CYPRESS_SEARCH_QUERY')) + cy.get(".card[data-cy='item-card']").should('be.visible') + }) + + it('with an invalid query term', () => { + const invalidQuery = 'asdfghjk' + testSetup({ + data: 'services/no-wares.json', + requestURL: `/wares.json?per_page=2000&q=${invalidQuery}`, + }) + + cy.get('input.search-bar').type(invalidQuery) + cy.get('button.search-button').click() + cy.url().should('include', `/browse?q=${invalidQuery}`) + cy.get('input.search-bar').should('have.value', invalidQuery) + cy.get("p[data-cy='no-results']").should('contain', `Your search for ${invalidQuery} returned no results`) }) }) }) - context('error while making a request to the api', () => { - before(() => { - loading = false - error = true - }) - it('should show an error message.', () => { - cy.get("div[role='alert']").should('be.visible').then(() => { - cy.log('Successfully hits an error.') + describe('renders a text box', () => { + it('showing the about text.', () => { + cy.get("section[data-cy='about-us-section']").should('exist').then(() => { + cy.log('Abouttext renders successfully.') }) }) }) - context('home page components are loading successfully, &', () => { - before(() => { - featuredServices = true - error = false - }) - it('should show the search bar.', () => { - cy.get("form[data-cy='search-bar']").should('exist').then(() => { - cy.log('Search bar renders successfully.') + describe('makes a call to the api', () => { + context('which when given an invalid access token', () => { + before(() => { + data = undefined + error = { + body: { + message: 'No access token provided.', + }, + statusCode: 403, + } + }) + + it('shows an error message', () => { + cy.get("div[role='alert']").should('be.visible').then(() => { + cy.log('Successfully hits an error.') + }) + cy.get("div[role='alert']").contains('No access token provided.') }) }) - it('should show the about text.', () => { - cy.get("section[data-cy='about-us-section']").should('exist').then(() => { - cy.log('Abouttext renders successfully.') + + context('which when returns no error or data', () => { + it('shows 3 placeholder cards loading', () => { + cy.get('p.placeholder-glow').should('have.length', 3).then(() => { + cy.log('Loading text displays correctly.') + }) }) }) - it('should show the featured services cards.', () => { - cy.get("div[data-cy='item-group']").should('exist').then(() => { - cy.log('Status bar renders successfully.') + + context('which when returns data', () => { + it('shows the featured services cards', () => { + cy.get("div[data-cy='item-group']").should('exist').then(() => { + cy.log('Status bar renders successfully.') + }) }) }) }) -}) \ No newline at end of file +}) diff --git a/cypress/fixtures/services/filtered-wares.json b/cypress/fixtures/services/filtered-wares.json new file mode 100644 index 00000000..4ac43915 --- /dev/null +++ b/cypress/fixtures/services/filtered-wares.json @@ -0,0 +1,22 @@ +{ + "ware_refs": [ + { + "id": 3456, + "slug": "test-ware", + "name": "Test Ware", + "snippet": "Here is a test ware snippet.", + "urls": { + "promo_image": "https://y.yarn.co/193fa4ae-a245-4f7a-ac9d-64bbebb18c8d_screenshot.jpg" + } + }, + { + "id": 4567, + "slug": "another-test-ware", + "name": "Another Test Ware", + "snippet": "Another test snippet.", + "urls": { + "promo_image": "https://cdn.drawception.com/images/panels/2017/7-2/jtqKRKSpyj-6.png" + } + } + ] +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js index dcf2c9a3..9ab32faa 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,47 +23,43 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + import { scientistApiBaseURL } from './e2e' // add a command to login that uses a session, so the user will remain logged in throughout the test file vs. needing to log in before each example. // source: https://github.com/nextauthjs/next-auth/discussions/2053#discussioncomment-1191016 Cypress.Commands.add('login', (username, password) => { cy.session([username, password], () => { - cy.intercept("/api/auth/session", { fixture: "session.json" }).as("session"); + cy.intercept('/api/auth/session', { fixture: 'session.json' }).as('session') - // Set the cookie for cypress. - // It has to be a valid cookie so next-auth can decrypt it and confirm its validity. - // This cookie also may need to be refreshed intermittently if it expires - cy.setCookie("next-auth.session-token", Cypress.env('TEST_SESSION_COOKIE')); + // Set the cookie for cypress. + // It has to be a valid cookie so next-auth can decrypt it and confirm its validity. + // This cookie also may need to be refreshed intermittently if it expires + cy.setCookie('next-auth.session-token', Cypress.env('TEST_SESSION_COOKIE')) }) }) -// intercepts requests and creates potential cases for loading, error, data, and empty data -// required params are action, defaultFixture, requestURL -// optional params such as data, loading, and error can be passed depending on the creation of test cases that are related to that specific api call +/** + * This command intercepts requests and returns the given stubbed response + * + * @param {string} alias - the alias to give the intercept (convention is to + * use the function name) + * @param {string} data - the fixture to return as the response data + * @param {object} error - the error object to return as the response error + * @param {string} requestURL - the URL to intercept + * + * @returns {object} - the stubbed response + */ Cypress.Commands.add('customApiIntercept', ({ - action, alias, data, defaultFixture, emptyFixture, error, errorCaseStatusCode, loading, requestURL + alias, data, error, requestURL }) => { - cy.intercept(action, scientistApiBaseURL + requestURL, (req) => { - switch (true) { - // reply with an empty response: both data and error will be undefined. - case loading: req.reply() - break - - // error will be defined - case error: req.reply({ statusCode: errorCaseStatusCode || 500 }) - break - - // reply with a request body- default status code is 200 - case data: req.reply({ fixture: defaultFixture }) - break - - // reply with the empty fixture is there is one, and the default as a backup. Allows us to isolate one api call at a time that may potentially respond with empty data. - case !data: req.reply({ fixture: emptyFixture || defaultFixture }) - break - - default: req.reply({ fixture: defaultFixture }) - break + cy.intercept(`${scientistApiBaseURL}${requestURL}`, (req) => { + const response = { + data: data && { fixture: data }, + error, } + + // falling back to an empty object mimics the loading state + return req.reply(response.data || response.error || {}) }).as(alias || 'customIntercept') -}) \ No newline at end of file +}) diff --git a/utils/api/base.js b/utils/api/base.js index b16c0ab1..1ae2a445 100644 --- a/utils/api/base.js +++ b/utils/api/base.js @@ -11,6 +11,7 @@ export const fetcher = (url, token) => { .then(res => res.data) .catch(error => { Sentry.captureException(error) + throw error }) }