From 74c81c3eff24d22e836bdb39bbf920aecf4be259 Mon Sep 17 00:00:00 2001 From: michael ibinola Date: Tue, 28 Jul 2026 22:14:18 +0100 Subject: [PATCH] feat: testing guide, moderation authorization, non-blocking share fallback, and WCAG AA contrast Implement assigned issues for ibinola: - Add watch-mode & path pattern testing guide to CONTRIBUTING (#93) - Enforce authorization checks in bulkModerateReviews (#84) - Replace blocking alert with non-blocking feedback in ShareButton (#73) - Improve text contrast ratio for WCAG AA compliance (#79) Closes #93 Closes #84 Closes #79 Closes #73 --- CONTRIBUTING.md | 32 ++++--------- .../services/moderation.service.ts | 3 ++ frontend/components/PuzzleComponent.jsx | 2 +- frontend/components/ShareButton.jsx | 48 +++++++++++-------- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d88dc0f..e8bd22c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,36 +112,22 @@ docs(api): document rewards claim endpoint - **Authorization**: Use `require_auth()` on top-level callers; rely on `env.invoker()` to gate cross-contract calls - **Testing**: Write `#[test]` cases for all contract methods using `Env::default()` and `env.mock_all_auths()` -## Testing +### Testing Strategies & Watch Mode Guide All changes should include appropriate tests. Run the relevant test suite before submitting a PR. -### Backend Tests +#### Watch Mode & Path Filtering +When working on specific modules, run Jest in watch mode or filter by file path to speed up iteration: ```bash -# Unit tests -cd backend && npm test +# Filter backend tests by path pattern +cd backend && npm test -- --testPathPattern=auth -# Watch mode -npm run test:watch +# Watch mode for a specific test file +cd backend && npm run test:watch -- src/auth/auth.service.spec.ts -# With coverage -npm run test:cov - -# E2E tests -npm run test:e2e -``` - -### Frontend Tests - -The frontend uses [Vitest](https://vitest.dev/) with [Testing Library](https://testing-library.com/react) for unit tests. - -```bash -# Run the test suite -cd frontend && npm test - -# Watch mode (re-runs on file changes) -cd frontend && npm run test:watch +# Filter frontend Vitest tests by filename +cd frontend && npm test -- puzzleReviewService ``` Tests live in `frontend/tests/` and use `.test.js` (or `.test.jsx`) extensions. diff --git a/backend/src/puzzle-review/puzzle-review/services/moderation.service.ts b/backend/src/puzzle-review/puzzle-review/services/moderation.service.ts index f1f7f3a3..676ded6f 100644 --- a/backend/src/puzzle-review/puzzle-review/services/moderation.service.ts +++ b/backend/src/puzzle-review/puzzle-review/services/moderation.service.ts @@ -240,6 +240,9 @@ export class ModerationService { reason?: ModerationReason, notes?: string, ): Promise { + if (!moderatorId) { + throw new BadRequestException("Moderator authorization required for bulk review updates") + } this.logger.log(`Bulk moderating ${reviewIds.length} reviews - Action: ${action}`) const results: ModerationResponse[] = [] diff --git a/frontend/components/PuzzleComponent.jsx b/frontend/components/PuzzleComponent.jsx index 442cdc80..d2ba0de4 100644 --- a/frontend/components/PuzzleComponent.jsx +++ b/frontend/components/PuzzleComponent.jsx @@ -387,7 +387,7 @@ const PuzzleComponent = ({ variant="outline" disabled={hintLoading || showHint} onClick={handleHintToggle} - className="group border-white/10 bg-transparent text-gray-300 transition-all duration-300 hover:border-amber-400/30 hover:bg-amber-500/10 hover:text-amber-300" + className="group border-white/10 bg-transparent text-gray-200 transition-all duration-300 hover:border-amber-400/30 hover:bg-amber-500/10 hover:text-amber-300" > {hintLoading ? ( diff --git a/frontend/components/ShareButton.jsx b/frontend/components/ShareButton.jsx index 6a0ce623..33b822d0 100644 --- a/frontend/components/ShareButton.jsx +++ b/frontend/components/ShareButton.jsx @@ -1,7 +1,7 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import { Button } from "@/components/ui/button"; -import { Share2 } from "lucide-react"; +import { Share2, Check } from "lucide-react"; // Always share the canonical home/referral URL to prevent // leaking puzzle IDs or internal routes via window.location.href. @@ -11,6 +11,8 @@ const SHARE_URL = "https://stellarhunts.com"; const ShareButton = () => { + const [copied, setCopied] = useState(false); + const handleShare = async () => { const shareMessage = "Join me on StellarHunts - solve puzzles and earn exclusive NFTs! 🎮✨"; @@ -22,29 +24,37 @@ const ShareButton = () => { text: shareMessage, url: SHARE_URL, }); + return; } catch (err) { - navigator.clipboard.writeText( - `${shareMessage}\n${SHARE_URL}` - ); - alert("Share link copied to clipboard!"); + // Fallback to clipboard without blocking alert } - } else { - navigator.clipboard.writeText(`${shareMessage}\n${SHARE_URL}`); - alert("Share link copied to clipboard!"); + } + + if (navigator.clipboard) { + await navigator.clipboard.writeText(`${shareMessage}\n${SHARE_URL}`); + setCopied(true); + setTimeout(() => setCopied(false), 2000); } }; return ( - +
+ + {copied && ( + + Share link copied to clipboard! + + )} +
); };