Skip to content

Commit

Permalink
feat: add some basic tests with actions and pre-commit updates. (#15)
Browse files Browse the repository at this point in the history
1. Added some basic tests:
  - When the user is not logged in:
    - Home screen should be protected by ProtectedRoute.
    - Footer should still work to route cross pages.
  - When user loged in:
- Home screen should be able to show the 'New Goal' on top of the
screen.

3. Update the ci workflow accordingly:
  - Now all new commits will need to pass the test first.
- All Firebase deployments and previews need to pass the test in order
to run.
  • Loading branch information
ZL-Asica authored Nov 1, 2024
1 parent 4bc0c8c commit bdda251
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 62 deletions.
73 changes: 37 additions & 36 deletions .github/workflows/firebase-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,62 @@ on:
push:
branches:
- main
paths:
- 'src/**' # Changes to the source code
- 'public/**' # Changes to the public folder
- 'firebase.json' # Changes to the Firebase configuration
- 'package.json' # Changes to the package.json file
- 'index.html' # Changes to the index.html file
- 'vitest.config.js' # Changes to the Vitest configuration
- '.firebaserc' # Changes to the Firebase configuration
- '.github/workflows/*' # Changes to the workflow file

concurrency:
group: firebase-deploy-${{ github.ref }}
cancel-in-progress: true

jobs:
# build:
# name: Build and Test
# runs-on: ubuntu-latest
build:
name: 🧱 Build and Test
runs-on: ubuntu-latest

steps:
- name: 📥 Checkout Code
uses: actions/checkout@v4

# steps:
# - name: Checkout Code
# uses: actions/checkout@v4
- name: 🛠️ Setup Node
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'npm'

# - name: Setup Node
# uses: actions/setup-node@v4
# with:
# node-version: 'lts/*'
- name: 📂 Install Packages
run: npm ci

# - name: Install Dependencies
# run: npm install
- name: 🧪 Run Tests with Vitest
run: npm run test:ci

# - name: Build Project
# run: npm run build
- name: 🔨 Build Project
run: npm run build

# - name: Run Tests
# run: npm test
deploy:
name: Deploy to Firebase
# needs: build
name: 🚀 Deploy to Firebase
needs: build # Only run this job if the build job is successful
runs-on: ubuntu-latest

steps:
- name: Checkout Code
- name: 📥 Checkout Code
uses: actions/checkout@v4

- name: Setup Node
- name: 🛠️ Setup Node
uses: actions/setup-node@v4
with:
node-version: 'lts/*'

- name: Cache dependencies 📦
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Packages install
run: npm ci

- name: Build Project
run: npm run build

- name: Install Firebase Tools
- name: 🌍 Install Firebase Tools
run: npm install -g firebase-tools

- name: Deploy to Firebase
- name: 🚀 Deploy to Firebase
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
run: firebase deploy --token "${{ secrets.FIREBASE_TOKEN }}" --non-interactive
61 changes: 50 additions & 11 deletions .github/workflows/firebase-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,84 @@ jobs:
issues: write

steps:
- name: Checkout code
- name: 📥 Checkout code
uses: actions/checkout@v4

- name: Setup Node
- name: 📦 Setup Node
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'npm'

- name: Install dependencies
run: npm install
- name: 📂 Install dependencies
run: npm ci

- name: Build the app
- name: 🔨 Build the app
run: npm run build

- name: Install Firebase CLI
- name: 🧪 Test the app with Vitest
run: npm run test:ci | tee test-results.txt

# 🚮 Clean up test results for comment
- name: 🚮 Clean up test results
run: sed -i 's/\x1b\[[0-9;]*m//g' test-results.txt # Removes ANSI color codes

# 📄 Format test results for better readability
- name: 📄 Format test results
run: |
{
echo "### 🧪 Test Results"
echo ""
if grep -q "failed" test-results.txt; then
# Extract failed test details if any
echo "❌ **Some tests failed**:"
echo '```'
grep -E "Test Files|Tests|Duration|failed" test-results.txt
echo '```'
echo ""
echo "💥 Please review the failed tests above."
else
# Format output for passing tests
grep -E "(Test Files|Tests|Duration)" test-results.txt | while read -r line; do
if [[ $line == *"Test Files"* ]]; then
echo "🗂 **Test Summary**: $line"
elif [[ $line == *"Tests"* ]]; then
echo "✅ **Tests Passed**: $line"
elif [[ $line == *"Duration"* ]]; then
echo "⏱️ **Total Duration**: $line"
fi
done
echo ""
echo "🎉 All tests passed successfully!"
fi
} > formatted-results.txt
- name: 🚀 Install Firebase CLI
run: npm install -g firebase-tools

- name: Deploy to Firebase Preview Channel
- name: 🌐 Deploy to Firebase Preview Channel
id: firebase_deploy
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
run: |
firebase hosting:channel:deploy pr-${{ github.event.number }} --expires 2d | tee deploy-output.txt
- name: Extract Preview URL
- name: 🔗 Extract Preview URL
id: extract_url
run: |
# Find the actual preview URL from the Firebase deploy output
URL=$(grep -oP 'https:\/\/[^\s]+pr-${{ github.event.number }}[^\s]+' deploy-output.txt)
echo "PREVIEW_URL=${URL}" >> $GITHUB_ENV
- name: Post preview link to PR
- name: 💬 Post test results and preview link to PR
uses: actions/github-script@v7
with:
script: |
const previewUrl = process.env.PREVIEW_URL;
const fs = require('fs');
const testResults = fs.readFileSync('formatted-results.txt', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `🚀 Preview for this PR is available at: ${previewUrl}`
body: `🚀 Preview for this PR is available at: ${previewUrl} \n\n ${testResults}`
});
17 changes: 14 additions & 3 deletions .github/workflows/label-pr-size.yml.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: 🛎️ Checkout repository
uses: actions/checkout@v4

- name: Determine PR size and add label
id: size-label
uses: "pascalgn/[email protected].4"
uses: pascalgn/[email protected].5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IGNORED: |
yarn.lock
package-lock.json
.pnp.*
dist/**
build/**
.cache/**
with:
sizes: >
{
Expand All @@ -43,7 +54,7 @@ jobs:
XXL: 'e99695', // Light coral
};
const sizeLabel = core.getInput('size-label-output') || '${{ steps.size-label.outputs.sizeLabel }}';
const sizeLabel = ${{ steps.size-label.outputs.sizeLabel }};
const color = labelsToColor[sizeLabel];
if (sizeLabel) {
Expand All @@ -66,7 +77,7 @@ jobs:
}
- name: Comment on large PRs
if: ${{ contains(steps.size-label.outputs.sizeLabel, 'XL') || contains(steps.size-label.outputs.sizeLabel, 'XXL') }}
if: ${{ contains(steps.size-label.outputs.sizeLabel, 'XL') }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
npx lint-staged
npm run test:ci
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"lint": "eslint src/**/*.{js,jsx}",
"lint:fix": "eslint src/**/*.{js,jsx} --fix",
"format": "prettier --write src/**/*.{js,jsx}",
"prepare": "husky"
"prepare": "husky",
"test:ci": "vitest run"
},
"lint-staged": {
"src/**/*.{js,jsx}": [
Expand Down
80 changes: 69 additions & 11 deletions src/App.test.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,76 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { UserProvider } from '@contexts/UserContext';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, expect, test } from 'vitest';

import App from './App';

describe('counter tests', () => {
test('Counter should be 0 at the start', () => {
render(<App />);
expect(screen.getByText('count is: 0')).toBeDefined();
describe('Check data before user logged in', () => {
test('Should show message when user is not logged in', async () => {
render(
<UserProvider>
<App />
</UserProvider>,
);

// Use `waitFor` to ensure the message appears after any async updates.
await waitFor(() => {
const message = screen.getByText('Please sign in to view this page');
expect(message).toBeTruthy();
});
});

test('Should redirect to Streak page when clicked on Streak tab', async () => {
render(
<UserProvider>
<App />
</UserProvider>,
);

// Wait for the loading spinner to disappear
await waitFor(() => {
expect(screen.queryByRole('progressbar')).toBeNull();
});

// Click the "Streak" tab
await act(async () => {
fireEvent.click(screen.getByText(/streak/i));
});

// Verify that the URL has changed to "/streak"
await waitFor(() => {
expect(window.location.pathname).to.equal('/streak');
});
});

test('Counter should increment by one when clicked', async () => {
render(<App />);
const counter = screen.getByRole('button');
fireEvent.click(counter);
expect(await screen.getByText('count is: 1')).toBeDefined();
test('Should able to redirect back to Home page from Streak page', async () => {
render(
<UserProvider>
<App />
</UserProvider>,
);

// Wait for the loading spinner to disappear
await waitFor(() => {
expect(screen.queryByRole('progressbar')).toBeNull();
});

// Click the "Streak" tab
await act(async () => {
fireEvent.click(screen.getByText(/streak/i));
});

// Verify that the URL has changed to "/streak"
await waitFor(() => {
expect(window.location.pathname).to.equal('/streak');
});

// Click the "Home" tab
await act(async () => {
fireEvent.click(screen.getByText(/home/i));
});

// Verify that the URL has changed to "/"
await waitFor(() => {
expect(window.location.pathname).to.equal('/');
});
});
});
52 changes: 52 additions & 0 deletions src/pages/Home.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { UserProvider } from '@contexts/UserContext';
import Home from '@pages/Home';
import { act, render, screen } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';

// Mock `@contexts/UserContext` to control user profile and updates
vi.mock('@contexts/UserContext', async () => {
const actual = await vi.importActual('@contexts/UserContext');
let mockUserProfile = {
uid: '123',
profilePic: '',
name: 'Test User',
goals: [],
streak: [],
};

return {
...actual,
useUser: () => ({
user: mockUserProfile,
loading: false,
updateProfile: vi.fn((updates) => {
mockUserProfile = { ...mockUserProfile, ...updates };
}),
}),
};
});

describe('Home Screen - No Goals', () => {
beforeEach(() => {
// Reset mockUserProfile for each test
vi.clearAllMocks();
});

test('Displays only "New Goal" field when there are no goals', async () => {
await act(async () => {
render(
<UserProvider>
<Home />
</UserProvider>,
);
});

// Check if "New Goal" text field is present
const newGoalInput = screen.getByLabelText('New Goal');
expect(newGoalInput).not.to.be.null;

// Ensure "New Microgoal" and "New Task" fields are not present initially
expect(screen.queryByLabelText('New Microgoal')).to.be.null;
expect(screen.queryByLabelText('New Task')).to.be.null;
});
});
1 change: 1 addition & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default defineConfig({
},
test: {
globals: true,
reporters: process.env.GITHUB_ACTIONS ? ['dot', 'github-actions'] : ['dot'],
environment: 'jsdom',
},
});

0 comments on commit bdda251

Please sign in to comment.