Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
123 changes: 123 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: PR Check

on:
pull_request:
branches: [main]

permissions:
contents: read
pull-requests: write

jobs:
checks:
name: PR checks
runs-on: ubuntu-latest

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

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install dependencies
id: install
run: yarn install --immutable

- name: Compile TypeScript (yarn tsc)
id: tsc
if: always() && steps.install.outcome == 'success'
run: yarn tsc

- name: Prettier check (yarn prettier:check)
id: prettier
if: always() && steps.install.outcome == 'success'
run: yarn prettier:check

- name: Run tests (yarn test)
id: test
if: always() && steps.install.outcome == 'success'
run: yarn test

- name: Build summary
id: summary
if: always()
run: |
status() {
case "$1" in
success) echo "✅ Passed" ;;
failure) echo "❌ Failed" ;;
skipped) echo "⚠️ Skipped" ;;
*) echo "➖ ${1:-not run}" ;;
esac
}

overall="✅ All checks passed."
failed=false
if [ "${{ steps.install.outcome }}" != "success" ] \
|| [ "${{ steps.tsc.outcome }}" != "success" ] \
|| [ "${{ steps.prettier.outcome }}" != "success" ] \
|| [ "${{ steps.test.outcome }}" != "success" ]; then
overall="❌ One or more checks failed."
failed=true
fi

{
echo "## CI check summary"
echo ""
echo "| Check | Command | Result |"
echo "| ----- | ------- | ------ |"
echo "| Install | \`yarn install\` | $(status "${{ steps.install.outcome }}") |"
echo "| TypeScript | \`yarn tsc\` | $(status "${{ steps.tsc.outcome }}") |"
echo "| Prettier | \`yarn prettier:check\` | $(status "${{ steps.prettier.outcome }}") |"
echo "| Tests | \`yarn test\` | $(status "${{ steps.test.outcome }}") |"
echo ""
echo "**Overall: $overall**"
} > summary.md

cat summary.md >> "$GITHUB_STEP_SUMMARY"
echo "failed=$failed" >> "$GITHUB_OUTPUT"

- name: Post summary to PR
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const marker = '<!-- ci-check-summary -->';
const body = `${marker}\n${fs.readFileSync('summary.md', 'utf8')}`;
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;

const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number,
});
const existing = comments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes(marker),
);

if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}

- name: Fail if any check failed
if: always() && steps.summary.outputs.failed == 'true'
run: |
echo "One or more checks failed."
exit 1
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test:all": "backstage-cli repo test --coverage",
"test:e2e": "playwright test",
"fix": "backstage-cli repo fix",
"lint": "backstage-cli repo lint --since origin/master",
"lint": "backstage-cli repo lint --since origin/main",
"lint:all": "backstage-cli repo lint",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
Expand Down
4 changes: 2 additions & 2 deletions plugins/team-leave-tracker-backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codeverse-gp/plugin-team-leave-tracker-backend",
"version": "1.0.0",
"version": "1.1.0",
"description": "A Backstage backend plugin for tracking team leave and vacation.",
"license": "MIT",
"main": "src/index.ts",
Expand Down Expand Up @@ -48,7 +48,7 @@
"author": "CodeVerse-GP",
"repository": {
"type": "git",
"url": "git+https://github.com/CodeVerse-GP/team-leave-tracker.git",
"url": "git+https://github.com/CodeVerse-GP/leave-tracker.git",
"directory": "plugins/team-leave-tracker-backend"
},
"backstage": {
Expand Down
4 changes: 2 additions & 2 deletions plugins/team-leave-tracker/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codeverse-gp/plugin-team-leave-tracker",
"version": "1.0.0",
"version": "1.1.0",
"description": "A Backstage plugin for tracking team leave and vacation.",
"license": "MIT",
"sideEffects": false,
Expand Down Expand Up @@ -51,7 +51,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/CodeVerse-GP/team-leave-tracker.git",
"url": "git+https://github.com/CodeVerse-GP/leave-tracker.git",
"directory": "plugins/team-leave-tracker"
},
"author": "CodeVerse-GP",
Expand Down
109 changes: 109 additions & 0 deletions plugins/team-leave-tracker/src/components/AddLeaveModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { fireEvent, render, screen, within } from '@testing-library/react';

import type { LeaveEntry, TeamMember } from '../data/model';
import { startOfToday, toIso } from '../data/model';
import { AddLeaveModal } from './AddLeaveModal';

const jane: TeamMember = {
id: 'user:default/jane',
name: 'Jane Doe',
initials: 'JD',
color: '#1A2332',
};

const todayIso = toIso(startOfToday());

const renderModal = (
props: Partial<React.ComponentProps<typeof AddLeaveModal>> = {},
) =>
render(
<AddLeaveModal
open
onClose={jest.fn()}
onSubmit={jest.fn()}
members={[jane]}
currentUserRef={jane.id}
{...props}
/>,
);

describe('AddLeaveModal', () => {
it('shows the "Add Leave" title and prefills the current member', () => {
renderModal();

const dialog = screen.getByRole('dialog');
expect(within(dialog).getByText('Add Leave')).toBeInTheDocument();
expect(screen.getByLabelText('Team member')).toHaveValue('Jane Doe');
});

it('shows the "Edit Leave" title and a save button when editing', () => {
const editingLeave: LeaveEntry = {
id: 'leave-1',
memberId: jane.id,
startDate: todayIso,
endDate: todayIso,
type: 'sick',
};

renderModal({ editingLeave });

const dialog = screen.getByRole('dialog');
expect(within(dialog).getByText('Edit Leave')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /save changes/i }),
).toBeInTheDocument();
});

it('validates that the end date is not before the start date', () => {
const onSubmit = jest.fn();
renderModal({ onSubmit });

fireEvent.change(screen.getByLabelText('Start date'), {
target: { value: '2026-07-10' },
});
fireEvent.change(screen.getByLabelText('End date'), {
target: { value: '2026-07-05' },
});
fireEvent.click(screen.getByRole('button', { name: /add leave/i }));

expect(
screen.getByText('End date cannot be before the start date.'),
).toBeInTheDocument();
expect(onSubmit).not.toHaveBeenCalled();
});

it('submits a valid leave and closes the modal', () => {
const onSubmit = jest.fn();
const onClose = jest.fn();
renderModal({ onSubmit, onClose });

fireEvent.change(screen.getByLabelText('Start date'), {
target: { value: '2026-07-05' },
});
fireEvent.change(screen.getByLabelText('End date'), {
target: { value: '2026-07-07' },
});
fireEvent.change(screen.getByLabelText('Leave type'), {
target: { value: 'conference' },
});
fireEvent.click(screen.getByRole('button', { name: /add leave/i }));

expect(onSubmit).toHaveBeenCalledWith({
memberId: jane.id,
startDate: '2026-07-05',
endDate: '2026-07-07',
type: 'conference',
note: undefined,
});
expect(onClose).toHaveBeenCalled();
});

it('calls onClose when cancelled', () => {
const onClose = jest.fn();
renderModal({ onClose });

fireEvent.click(screen.getByRole('button', { name: /cancel/i }));

expect(onClose).toHaveBeenCalled();
});
});
Loading
Loading