Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ad562e1
Update default port to 5001 and add IDE-specific .gitignore entries
mhgolestan Aug 1, 2025
1ababaa
Add GitHub Actions workflow for Hello World!
mhgolestan Aug 1, 2025
37f6143
Add step to display current date in GitHub Actions workflow
mhgolestan Aug 1, 2025
bd0ef52
Add step to display directory content in GitHub Actions workflow
mhgolestan Aug 1, 2025
08640c4
Add deployment pipeline workflow to GitHub Actions
mhgolestan Aug 1, 2025
8d087e8
Update ESLint plugin versions and improve code consistency in app.js
mhgolestan Aug 1, 2025
0370d91
Fix "Next" link bug in PokemonPage and add test step to GitHub Action…
mhgolestan Aug 1, 2025
6e26bdc
Add Playwright for E2E testing and update pipeline to include E2E tests
mhgolestan Aug 1, 2025
d7b61f2
Add /version endpoint to verify deployment changes
mhgolestan Aug 2, 2025
9fb4987
Add build script with install, build, and production start steps
mhgolestan Aug 2, 2025
2640f13
Add deployment step to GitHub Actions pipeline using Render deploymen…
mhgolestan Aug 2, 2025
d713dc0
Replace Render deployment action with deploy hook curl request in Git…
mhgolestan Aug 2, 2025
e374184
Update Render deployment step to use POST request with deploy hook UR…
mhgolestan Aug 2, 2025
75ef4a0
Add /health endpoint to check application status
mhgolestan Aug 2, 2025
cf33508
Simulate a broken deployment on /health endpoint
mhgolestan Aug 2, 2025
8888efd
Remove constant condition and exception logic from /health endpoint
mhgolestan Aug 2, 2025
88731ba
Add pull_request trigger to GitHub Actions pipeline
mhgolestan Aug 2, 2025
e9b2ab7
Add PR test step to GitHub Actions pipeline
mhgolestan Aug 2, 2025
e7c4c2e
Update deployment condition in GitHub Actions to trigger only on push…
mhgolestan Aug 2, 2025
ecd1583
Remove redundant PR test step from GitHub Actions pipeline
mhgolestan Aug 2, 2025
a64ee9a
Merge pull request #1 from mhgolestan/pr_test
mhgolestan Aug 2, 2025
df5f2c5
Remove commented-out Render deployment action from GitHub Actions pip…
mhgolestan Aug 2, 2025
66b2106
Merge remote-tracking branch 'origin/pr_test' into pr_test
mhgolestan Aug 2, 2025
338d9a9
Merge pull request #2 from mhgolestan/pr_test
mhgolestan Aug 2, 2025
c597989
Add GitHub Actions step to auto-bump version and push tags with dry run
mhgolestan Aug 2, 2025
9ab97bb
Add conditional to tag_release step in GitHub Actions to trigger only…
mhgolestan Aug 2, 2025
d79ab4b
Add checkout step to tag_release job in GitHub Actions pipeline to fe…
mhgolestan Aug 2, 2025
f540bcb
Remove dry-run mode from version bump action in GitHub Actions pipeline
mhgolestan Aug 2, 2025
e8a93cb
Bump a #minor version
mhgolestan Aug 2, 2025
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
78 changes: 42 additions & 36 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
module.exports = {
"env": {
"browser": true,
"es6": true,
"jest/globals": true
'env': {
'browser': true,
'es6': true,
'jest/globals': true,
'node': true,
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
'extends': [
'eslint:recommended',
'plugin:react/recommended'
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
'parserOptions': {
'ecmaFeatures': {
'jsx': true
},
"ecmaVersion": 2018,
"sourceType": "module"
'ecmaVersion': 2018,
'sourceType': 'module'
},
"plugins": [
"react", "jest"
'plugins': [
'react', 'jest'
],
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
'settings': {
'react': {
'version': '18.2.0'
}
},
'rules': {
'indent': [
'error',
2
],
'linebreak-style': [
'error',
'unix'
],
"quotes": [
"error",
"single"
'quotes': [
'error',
'single'
],
"semi": [
"error",
"never"
'semi': [
'error',
'never'
],
"eqeqeq": "error",
"no-trailing-spaces": "error",
"object-curly-spacing": [
"error", "always"
'eqeqeq': 'error',
'no-trailing-spaces': 'error',
'object-curly-spacing': [
'error', 'always'
],
"arrow-spacing": [
"error", { "before": true, "after": true }
'arrow-spacing': [
'error', { 'before': true, 'after': true }
],
"no-console": "error",
"react/prop-types": 0
'no-console': 'error',
'react/prop-types': 0
}
}
20 changes: 20 additions & 0 deletions .github/workflows/hello.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Hello World!
on:
push:
branches:
- main

jobs:
hello_world_job:
runs-on: ubuntu-latest
steps:
- name: Say hello
run: |
echo "Hello World!"

- name: Display the date
run: date "+%A, %B %d, %Y at %T"

- name: Directory content
run: ls -l

47 changes: 47 additions & 0 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Deployment pipeline

on:
push:
branches:
- main
pull_request:
branches: [main]
types: [opened, synchronize]

jobs:
simple_deployment_pipeline:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Check style
run: npm run eslint
- name: Run unit and integration tests
run: npm run test
- name: Build
run: npm run build
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run e2e tests
run: npm run test:e2e
- name: Trigger deployment with deploy hook
if: ${{ github.event_name == 'push' }}
run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK_URL }}

tag_release:
if: ${{ github.event_name == 'push' }}
needs: [simple_deployment_pipeline]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Bump version and push tag
uses: anothrNick/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEFAULT_BUMP: patch
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
dist/
node_modules/
node_modules/

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 14 additions & 6 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
const express = require("express");
const app = express();
const express = require('express')
const app = express()

// get the port from env variable
const PORT = process.env.PORT || 5000;
const PORT = process.env.PORT || 5001

app.use(express.static("dist"));
app.use(express.static('dist'))

/* eslint-disable no-console */
app.listen(PORT, () => {
console.log(`server started on port ${PORT}`);
});
console.log(`server started on port ${PORT}`)
})
/* eslint-enable no-console */
app.get('/version', (req, res) => {
res.send('1') // change this string to ensure a new version deployed
})
app.get('/health', (req, res) => {
res.send('ok')
})
7 changes: 7 additions & 0 deletions build_step.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

echo "Build script"

npm install
npm run build
npm run start-prod
56 changes: 56 additions & 0 deletions e2e-tests/e2e.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const { test, describe, expect } = require('@playwright/test')

describe('Pokedex', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
})

test('front page can be opened', async ({ page }) => {
await expect(page.getByText('ivysaur')).toBeVisible()
await expect(page.getByText('Pokémon and Pokémon character names are trademarks of Nintendo.')).toBeVisible()
})

test('can navigate to individual pokemon page', async ({ page }) => {
await page.getByText('ivysaur').click()
await expect(page.getByText('ivysaur', { exact: true })).toBeVisible()
await expect(page.getByTestId('stats')).toBeVisible()
})

test('navigation between pokemons works', async ({ page }) => {
await page.getByText('ivysaur').click()
await page.getByText('Next').click()
await expect(page.getByText('venusaur', { exact: true })).toBeVisible()
await page.getByText('Previous').click()
await expect(page.getByText('ivysaur', { exact: true })).toBeVisible()
await page.getByText('Home').click()
await expect(page.getByText('ivysaur')).toBeVisible()
})

test('pokemon details are displayed correctly', async ({ page }) => {
await page.getByText('ivysaur').click()
await expect(page.locator('.pokemon-image')).toBeVisible()
const statsTable = page.getByTestId('stats')
await expect(statsTable).toBeVisible()
await expect(page.locator('td.pokemon-stats-name', { hasText: 'hp' })).toBeVisible()
await expect(page.locator('td.pokemon-stats-name', { hasText: 'special attack' })).toBeVisible()
await expect(page.locator('td.pokemon-stats-name', { hasText: 'special defense' })).toBeVisible()
await expect(page.locator('.pokemon-abilities')).toBeVisible()
})

test('pokemon loading state and data fetching works', async ({ page }) => {
await page.route('**/api.co/api/v2/pokemon/**', async (route) => {
await new Promise(resolve => setTimeout(resolve, 1000)) // Add 1s delay
await route.continue()
})
await page.getByText('ivysaur').click()
await expect(page.locator('.pokemon-info')).toBeVisible()
})

test('pokemon type affects the page styling', async ({ page }) => {
await page.getByText('ivysaur').click()
await expect(page.locator('.pokemon-page')).toBeVisible()
const pokemonPage = page.locator('.pokemon-page')
const typeClass = await pokemonPage.getAttribute('class')
expect(typeClass).toMatch(/pokemon-type-\w+/)
})
})
Loading