Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e50e1f0
fix Grid2 import
derekmborges Apr 18, 2023
152585f
add cypress and failing test to validate ci in GHA
derekmborges Apr 19, 2023
d143138
add firebase env vars to test job
derekmborges Apr 20, 2023
25799ba
add firebase cli to devDependencies and token to emulators script
derekmborges Apr 20, 2023
dfc0189
trying wait-for-it
derekmborges Apr 20, 2023
3108de3
increase wait-for-it timeout to 60 seconds
derekmborges Apr 20, 2023
8ab1a0e
make waits strict
derekmborges Apr 20, 2023
00e164e
pass test and stop app after tests
derekmborges Apr 20, 2023
8175fa7
try fixing kill-emulators
derekmborges Apr 20, 2023
d29ae88
try backgrounding the app
derekmborges Apr 20, 2023
be3ad3d
cleanly quit emulators
derekmborges Apr 20, 2023
b71f287
add back in kill calls
derekmborges Apr 20, 2023
f5d91d3
make wait-for-it fail if services aren't up
derekmborges Apr 20, 2023
09bf341
try fixing the script
derekmborges Apr 20, 2023
05dc0c6
try again
derekmborges Apr 20, 2023
73c5f3b
update checkout actions to v3
derekmborges Apr 21, 2023
4794b23
add emulators env var to ci job
derekmborges Apr 21, 2023
deb14ec
change firebase-tools to 11.25.2
derekmborges Apr 21, 2023
b09e978
turn off emulators ui
derekmborges Apr 21, 2023
17fa40d
remove wait for emulator ui
derekmborges Apr 21, 2023
711723b
debugging emulators
derekmborges Apr 21, 2023
74e622d
pass in project id to emulators
derekmborges Apr 21, 2023
ef317e7
trying emulators:exec
derekmborges Apr 21, 2023
2288303
adding token to emulators:exec
derekmborges Apr 21, 2023
1007d9d
require test to pass to pass job
derekmborges Apr 22, 2023
d335bf4
ci working
derekmborges Apr 22, 2023
f71d814
add node_modules cache
derekmborges Apr 22, 2023
f368ce1
fix cache key
derekmborges Apr 22, 2023
2a4cf68
add missing cache block
derekmborges Apr 22, 2023
a207944
finish projects.cy.ts
derekmborges Apr 23, 2023
8e1537d
add pairees.cy.ts with 1 test
derekmborges Apr 23, 2023
303088a
cache emulators binary in workflow
derekmborges Apr 23, 2023
5d146b8
try to fix the workflow cache
derekmborges Apr 23, 2023
caf8482
use log file to wait for react build before testing
derekmborges Apr 29, 2023
ada1655
temp ci change to cache dependencies
derekmborges Apr 29, 2023
d1ffc58
temp ci change to cache dependencies 2
derekmborges Apr 29, 2023
9c49395
testing using cache
derekmborges Apr 29, 2023
35c5cb4
install cypress binary in gha
derekmborges Apr 29, 2023
6652ea5
fix cypress install
derekmborges Apr 29, 2023
f37e456
fix tests and add 2 pairees tests
derekmborges Apr 29, 2023
398cbad
trying to fix projects tests
derekmborges Apr 29, 2023
7161fdd
add pair assignment and recording coverage
derekmborges Apr 30, 2023
6fc25e5
add skipped test for deleting pairee
derekmborges May 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build App on Pull Request
name: Build and Test App in PRs
'on':
pull_request:
branches:
Expand All @@ -7,6 +7,47 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
**/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Build
run: yarn && yarn build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache dependencies
id: cache-dependencies
uses: actions/cache@v3
with:
path: |
**/node_modules
**/.cache/Cypress
**/.cache/firebase/emulators
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Install Dependencies
run: yarn
# - name: Cache emulators binary
# uses: actions/cache@v3
# with:
# path: |
# **/.cache/firebase/emulators
# key: ${{ runner.os }}
- name: Install Cypress binary
run: npx cypress install
if: steps.cache-dependencies.outputs.cache-hit == 'true'
- name: Test
run: yarn ci
env:
REACT_APP_FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
REACT_APP_FIREBASE_AUTH_DOMAIN: ${{ secrets.FIREBASE_AUTH_DOMAIN }}
REACT_APP_FIREBASE_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }}
REACT_APP_FIREBASE_STORAGE_BUCKET: ${{ secrets.FIREBASE_STORAGE_BUCKET }}
REACT_APP_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.FIREBASE_MESSAGING_SENDER_ID }}
REACT_APP_FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
NODE_ENV: development
REACT_APP_DATA_SOURCE: emulators
8 changes: 7 additions & 1 deletion .github/workflows/pages-hosting-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
**/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Deploy
env:
GIT_REPO: github.com/derekmborges/pairminator.git
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ yarn-error.log*

.env
.firebase/
*debug.log*
*.log

# cypress
cypress/screenshots/
cypress/videos/
cypress/downloads/
14 changes: 14 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: 'http://localhost:3000/pairminator#',
env: {
"NODE_ENV": "development",
"REACT_APP_DATA_SOURCE": "emulators",
}
},
});
69 changes: 69 additions & 0 deletions cypress/e2e/pairees.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { v4 as uuidv4 } from 'uuid'

describe('Pairees', () => {
beforeEach(() => {
cy.visit('/new')
cy.get('[data-cy=project-name]').type(uuidv4())
cy.get('[data-cy=project-password').type(uuidv4())
cy.get('[data-cy=submit]').click()
})

afterEach(() => {
cy.get('[data-cy=sign-out]').click()
})

it('allows user to add multiple pairees', () => {
const pairees = ['John', 'Stacey']
for (let pairee of pairees) {
cy.get('[data-cy=new-pairee-name').type(pairee)
cy.get('[data-cy=add-pairee]').click()
cy.get('[data-cy=new-pairee-alert]').should('have.text', 'Pairee added successfully')
}

cy.get('[data-cy=pairee-name]').should('have.length', 2)
cy.get('[data-cy=pairee-name]').each((label, i) => {
expect(label.text()).to.equal(pairees[i])
})
})

it('clears out pairee text field upon adding', () => {
cy.get('[data-cy=new-pairee-name').type('Somebody')
cy.get('[data-cy=add-pairee]').click()
cy.get('[data-cy=new-pairee-alert]').should('have.text', 'Pairee added successfully')
cy.get('[data-cy=new-pairee-name').should('have.value', '')
})

it('prevents user from adding pairee with the same name', () => {
const name = 'Alex'
cy.get('[data-cy=new-pairee-name').type(name)
cy.get('[data-cy=add-pairee]').click()
cy.get('[data-cy=new-pairee-alert]').should('have.text', 'Pairee added successfully')

cy.get('[data-cy=new-pairee-name').type(name)
cy.get('[data-cy=add-pairee]').click()

cy.get('[data-cy=pairee-add-error]').should('have.text', 'Pairee name already exists.')
})

// TODO: fix the delete modal bug and re-activate test
it.skip('allows user to delete a pairee', () => {
const pairees = ['John', 'Stacey']
for (let pairee of pairees) {
cy.get('[data-cy=new-pairee-name').type(pairee)
cy.get('[data-cy=add-pairee]').click()
cy.get('[data-cy=new-pairee-alert]').should('have.text', 'Pairee added successfully')
}
cy.get('[data-cy=pairee-name]').should('have.length', 2)
const paireeToDelete = pairees[0]

cy.get(`[data-cy=pairee-row-${paireeToDelete}]`).trigger('mouseover')
cy.get('[data-cy=pairee-delete-button]').click()

cy.get('[data-cy=delete-modal-text').should('have.text', `Remove ${paireeToDelete} from pairees?`)

cy.get('[data-cy=pairee-confirm-delete-button]').click()

cy.get('[data-cy=deleted-pairee-alert]').should('have.text', 'Pairee deleted successfully')
cy.get('[data-cy=pairee-name]').should('have.length', 1).should('have.text', pairees[1])
})
})
74 changes: 74 additions & 0 deletions cypress/e2e/pairs.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { v4 as uuidv4 } from 'uuid'
import { times } from 'lodash'

describe('Pairs', () => {
beforeEach(() => {
cy.visit('/new')
cy.get('[data-cy=project-name]').type(uuidv4())
cy.get('[data-cy=project-password').type(uuidv4())
cy.get('[data-cy=submit]').click()
})

afterEach(() => {
cy.get('[data-cy=sign-out]').click()
})

const addPairees = (numPairees: number) => {
let pairees: string[] = []
times(numPairees, (n: number) => {
pairees.push(`Pairee${n + 1}`)
})
for (let pairee of pairees) {
cy.get('[data-cy=new-pairee-name').type(pairee)
cy.get('[data-cy=add-pairee]').click()
cy.get('[data-cy=new-pairee-alert]').should('have.text', 'Pairee added successfully')
cy.get('body').click() // Make toast disappear
}
}

it('allows user to assign pairs for even number of pairees', () => {
const expectedNumLanes = 2
addPairees(expectedNumLanes * 2)
cy.get('[data-cy=no-current-pairs-label').should('have.text', 'No pairs have been assigned yet.')

cy.get('[data-cy=assign-pairs-button]').click()

cy.get('[data-cy=lane-name]').should('have.length', expectedNumLanes)
cy.get('[data-cy=lane-name]').each((label, i) => {
const lane = i + 1
expect(label.text()).to.equal(`Lane ${lane}`)
})
cy.get(`[data-cy=pairee1-name]`).should('have.length', expectedNumLanes)
cy.get(`[data-cy=pairee2-name]`).should('have.length', expectedNumLanes)
})

it('allows user to assign pairs for odd number of pairees', () => {
const expectedNumLanes = 3
addPairees(expectedNumLanes * 2 - 1)
cy.get('[data-cy=no-current-pairs-label').should('have.text', 'No pairs have been assigned yet.')

cy.get('[data-cy=assign-pairs-button]').click()

cy.get('[data-cy=lane-name]').should('have.length', expectedNumLanes)
cy.get('[data-cy=lane-name]').each((label, i) => {
const lane = i + 1
expect(label.text()).to.equal(`Lane ${lane}`)
})
cy.get(`[data-cy=pairee1-name]`).should('have.length', expectedNumLanes)
cy.get(`[data-cy=pairee2-name]`).should('have.length', expectedNumLanes - 1)
})

it('allows user to record assigned pairs and see the assignment in the history', () => {
addPairees(2)
cy.get('[data-cy=no-current-pairs-label').should('have.text', 'No pairs have been assigned yet.')
cy.get('[data-cy=no-history-label]').should('have.text', 'Record pairs to see a recent history.')

cy.get('[data-cy=assign-pairs-button]').click()
cy.get('[data-cy=lane-name]').should('have.length', 1)


cy.get('[data-cy=record-pairs-button]').click()
cy.get('[data-cy=recorded-pairs-alert]').should('have.text', 'Pairs recorded')
cy.get('[data-cy=history-record]').should('have.length', 1)
})
})
70 changes: 70 additions & 0 deletions cypress/e2e/projects.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { v4 as uuidv4 } from 'uuid'

describe('Project Creation - Success', () => {
beforeEach(() => {
cy.visit('/new')
})

afterEach(() => {
cy.get('[data-cy=sign-out]').click()
})

it('allows user to create a new project', () => {
cy.get('[data-cy=page-header]').should('contain.text', 'Create a project')

const projectName = uuidv4()
cy.get('[data-cy=project-name]').type(projectName)

cy.get('[data-cy=project-password').type(uuidv4())

cy.get('[data-cy=submit]').click()

cy.location().should((location) => {
expect(location.href).to.eq('http://localhost:3000/pairminator#/dashboard')
})

cy.get('[data-cy=current-project-name]').should('have.text', projectName)
})
})

describe('Project Creation - Validation', () => {
beforeEach(() => {
cy.visit('/new')
})

it('form submit is disabled when either field is below minimum character requirement', () => {
cy.get('[data-cy=submit]').should('not.be.enabled')

cy.get('[data-cy=project-name]').type('1') // 1/2 chars
cy.get('[data-cy=submit]').should('not.be.enabled')

cy.get('[data-cy=project-password').type('12345') // 5/6 chars
cy.get('[data-cy=submit]').should('not.be.enabled')

cy.get('[data-cy=project-name]').type('12') // minimum 2 chars
cy.get('[data-cy=project-password').type('123456') // minimum 6 chars

cy.get('[data-cy=submit]').should('be.enabled')
})

it('prevents user from creating duplicate project name', () => {
cy.get('[data-cy=page-header]').should('contain.text', 'Create a project')

const projectName = uuidv4()
cy.get('[data-cy=project-name]').type(projectName)
cy.get('[data-cy=project-password').type(uuidv4())
cy.get('[data-cy=submit]').click()

cy.get('[data-cy=current-project-name]').should('have.text', projectName)
cy.get('[data-cy=sign-out]').click()

cy.get('[data-cy=create-project-button]').click()

// re-use same project name
cy.get('[data-cy=project-name]').type(projectName)
cy.get('[data-cy=project-password').type(uuidv4())
cy.get('[data-cy=submit]').click()

cy.get('[data-cy=new-project-error]').should('have.text', 'Project name is not available')
})
})
43 changes: 43 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts 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('logout', () => {
cy.get('[data-cy=sign-out]').click()
})

Cypress.Commands.add('login', (name, password) => {
// TODO: complete
})
//
//
// -- 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 will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
20 changes: 20 additions & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts 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'

// Alternatively you can use CommonJS syntax:
// require('./commands')
Loading