Skip to content

Commit

Permalink
✅ [web] Add integration test framework (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhinodavid authored May 10, 2020
1 parent 29174fa commit 81b5840
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ packages/**/__sapper__/**
packages/**/coverage/**
packages/**/dist/**
packages/**/node_modules/**
packages/server/cypress/**
packages/**/cypress/**
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ config.ts
.redisuri
packages/server/cypress/screenshots/**/*(failed).png

# web integration test
packages/web/cypress/screenshots/**/*(failed).png

# misc
.DS_Store
.expo/*
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@
"build:server": "yarn build:local-packages && yarn workspace @upswyng/server build",
"build:web": "yarn build:local-packages && yarn workspace @upswyng/web build",
"clean": "node clean.js",
"heroku-postbuild": "yarn build:local-packages && yarn workspace @upswyng/server build",
"heroku-postbuild": "yarn build:web && yarn build:server",
"lint": "tsc --noEmit && eslint \"packages/**/src/**/*.{js,jsx,ts,tsx}\" --fix",
"lint:ci": "tsc --noEmit && eslint \"packages/**/src/**/*.{js,jsx,ts,tsx}\" --quiet",
"test": "concurrently -n common,server,web -c yellow,blue,cyan \"yarn workspace @upswyng/common test\" \"yarn workspace @upswyng/server test\" \"yarn workspace @upswyng/web test\"",
"test:ci": "yarn test --kill-others-on-fail"
"test": "concurrently -n common,server -c yellow,blue \"yarn workspace @upswyng/common test\" \"yarn workspace @upswyng/server test\" && yarn workspace @upswyng/web test",
"test:ci": "concurrently -n common,server -c yellow,blue \"yarn workspace @upswyng/common test\" \"yarn workspace @upswyng/server test\" --kill-others-on-fail && yarn workspace @upswyng/web test"
},
"version": "0.1.0",
"workspaces": [
Expand Down
4 changes: 4 additions & 0 deletions packages/web/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:3001",
"video": false
}
46 changes: 46 additions & 0 deletions packages/web/cypress/fixtures/api/weather.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"base": "stations",
"clouds": {
"all": 1
},
"cod": 200,
"coord": {
"lat": 40.02,
"lon": -105.27
},
"createdDate": "2020-05-09T22:08:39.377Z",
"dt": 1589062048,
"id": 5574991,
"lifespan": 60000,
"main": {
"feels_like": 48.11,
"humidity": 42,
"pressure": 1023,
"temp": 57.69,
"temp_max": 60.8,
"temp_min": 54
},
"name": "Boulder",
"sys": {
"country": "US",
"id": 3406,
"sunrise": 1589025069,
"sunset": 1589076233,
"type": 1
},
"timezone": -21600,
"visibility": 16093,
"weather": [
{
"description": "clear sky",
"icon": "01d",
"id": 800,
"main": "Clear"
}
],
"wind": {
"deg": 70,
"gust": 17.22,
"speed": 11.41
}
}
13 changes: 13 additions & 0 deletions packages/web/cypress/integration/web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
describe("Web Client", () => {
before(() => {
cy.server();
cy.route("POST", "/api/alert/search", { count: 0, alerts: [] });
cy.route("/api/weather*", "fixture:api/weather").as("getWeather");
cy.visit("/");
});

it("shows the weather", () => {
cy.wait("@getWeather");
cy.contains("58°");
});
});
20 changes: 20 additions & 0 deletions packages/web/cypress/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

export default function(
_on: Cypress.PluginEvents,
_config: Cypress.PluginConfigOptions
) {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}
27 changes: 27 additions & 0 deletions packages/web/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

export {};
17 changes: 17 additions & 0 deletions packages/web/cypress/support/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import "./commands";
11 changes: 11 additions & 0 deletions packages/web/cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"baseUrl": "../../../node_modules",
"isolatedModules": false,
"lib": ["es5", "dom"],
"strict": true,
"target": "es5",
"types": ["cypress"]
},
"include": ["**/*.ts"]
}
11 changes: 9 additions & 2 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@
"@testing-library/react": "^9.4.1",
"@testing-library/react-hooks": "^1.0.4",
"@upswyng/types": "0.1.0",
"husky": "^1.3.1"
"cypress": "^4.5.0",
"husky": "^1.3.1",
"npm-run-all": "^4.1.5",
"start-server-and-test": "^1.11.0"
},
"husky": {
"hooks": {
Expand All @@ -54,10 +57,14 @@
"name": "@upswyng/web",
"private": true,
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run",
"build": "SKIP_PREFLIGHT_CHECK=true yarn workspace @upswyng/common build && react-scripts build",
"eject": "SKIP_PREFLIGHT_CHECK=true react-scripts eject",
"start": "PORT=3001 SKIP_PREFLIGHT_CHECK=true react-scripts start",
"test": "SKIP_PREFLIGHT_CHECK=true react-scripts test --watchAll=false"
"test": "run-p -n test:integration test:unit",
"test:integration": "BROWSER=none REACT_SERVER_URI=http://localhost:3001 start-server-and-test start 3001 'cypress run'",
"test:unit": "SKIP_PREFLIGHT_CHECK=true react-scripts test --watchAll=false"
},
"version": "0.1.0"
}
118 changes: 78 additions & 40 deletions packages/web/src/components/useSearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,88 @@ import { useEffect, useState } from "react";
import { TStatusFetch } from "@upswyng/types";
import algoliaSearch from "algoliasearch";

declare const process: TEnvVariables;

const algoliaClient = algoliaSearch(
process.env.REACT_APP_ALGOLIA_APP_ID,
process.env.REACT_APP_ALGOLIA_SEARCH_API_KEY
);
const searchIndex = algoliaClient.initIndex(
process.env.REACT_APP_ALGOLIA_INDEX_NAME
);

function useSearchResults(
query: string
): [TStatusFetch, TAlgoliaResponse | null] {
const [status, setStatus] = useState<TStatusFetch>(
TStatusFetch.STATUS_NOT_FETCHED
declare const process: TEnvVariables & {
env: { NODE_ENV: string | undefined };
};

const {
NODE_ENV,
REACT_APP_ALGOLIA_APP_ID,
REACT_APP_ALGOLIA_SEARCH_API_KEY,
REACT_APP_ALGOLIA_INDEX_NAME,
} = process.env;

// Development/CI mode:
// If the environment is not production and algolia information isn't complete,
// set up a handler that just logs queries
const logOnly =
NODE_ENV !== "production" &&
(!REACT_APP_ALGOLIA_APP_ID ||
!REACT_APP_ALGOLIA_SEARCH_API_KEY ||
!REACT_APP_ALGOLIA_INDEX_NAME);

let algoliaClient: algoliaSearch.Client;
let searchIndex: algoliaSearch.Index;

if (logOnly) {
console.warn(
"Algolia search env vars not provided. Starting in Development Mode."
);
const [searchResults, setSearchResults] = useState<null | TAlgoliaResponse>(
null
console.warn(`\tREACT_APP_ALGOLIA_APP_ID:\t${REACT_APP_ALGOLIA_APP_ID}`);
console.warn(
`\tREACT_APP_ALGOLIA_SEARCH_API_KEY:\t${REACT_APP_ALGOLIA_SEARCH_API_KEY}`
);
console.warn(
`\tREACT_APP_ALGOLIA_INDEX_NAME:\t${REACT_APP_ALGOLIA_INDEX_NAME}`
);
} else {
algoliaClient = algoliaSearch(
process.env.REACT_APP_ALGOLIA_APP_ID,
process.env.REACT_APP_ALGOLIA_SEARCH_API_KEY
);
searchIndex = algoliaClient.initIndex(
process.env.REACT_APP_ALGOLIA_INDEX_NAME
);
}

useEffect(() => {
if (query) {
const getSearchResults = async (query: string): Promise<void> => {
try {
setStatus(TStatusFetch.STATUS_FETCHING);
setSearchResults(null);
const searchResults = (await searchIndex.search(
query
)) as TAlgoliaResponse;

setStatus(TStatusFetch.STATUS_FETCH_SUCCESS);
setSearchResults(searchResults);
} catch (err) {
// TODO: log this error
setStatus(TStatusFetch.STATUS_FETCH_ERROR);
setSearchResults(null);
}
};

getSearchResults(query);
const useSearchResults = logOnly
? (query: string): [TStatusFetch, TAlgoliaResponse | null] => {
console.warn(`Algolia search queried in dev mode: ${query}`);
return [TStatusFetch.STATUS_FETCH_ERROR, null];
}
}, [query]);
: (query: string): [TStatusFetch, TAlgoliaResponse | null] => {
const [status, setStatus] = useState<TStatusFetch>(
TStatusFetch.STATUS_NOT_FETCHED
);
const [
searchResults,
setSearchResults,
] = useState<null | TAlgoliaResponse>(null);

return [status, searchResults];
}
useEffect(() => {
if (query) {
const getSearchResults = async (query: string): Promise<void> => {
try {
setStatus(TStatusFetch.STATUS_FETCHING);
setSearchResults(null);
const searchResults = (await searchIndex.search(
query
)) as TAlgoliaResponse;

setStatus(TStatusFetch.STATUS_FETCH_SUCCESS);
setSearchResults(searchResults);
} catch (err) {
// TODO: log this error
setStatus(TStatusFetch.STATUS_FETCH_ERROR);
setSearchResults(null);
}
};

getSearchResults(query);
}
}, [query]);

return [status, searchResults];
};

export default useSearchResults;
2 changes: 1 addition & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"resolveJsonModule": true,
"types": ["jest"]
},
"exclude": ["node_modules", "packages/server/cypress/**/*"]
"exclude": ["node_modules", "packages/**/cypress/**/*"]
}

0 comments on commit 81b5840

Please sign in to comment.