From 7fcda3ec2215bcd4c58b751792e1eb25d0dbf80e Mon Sep 17 00:00:00 2001 From: OMIN MAN Date: Sat, 30 May 2026 17:13:14 +0000 Subject: [PATCH] Fix test failures and sync issue in study group utilities This PR pushes the latest fixes for test failures and updates study group state handling. Changes: - Guard `scrollIntoView` in `GroupDiscussionThread` for JSDOM tests. - Keep leaderboard and group query results in sync with persisted storage in `useStudyGroups`. - Stabilize async validation promise handling in `AsyncValidationManager`. - Fix path imports in `TipForm` tests for the current test environment. --- .husky/pre-push | 3 + package-lock.json | 90 +++++++++++++++++++ .../social/GroupDiscussionThread.tsx | 5 +- src/app/hooks/useStudyGroups.tsx | 12 ++- .../tipping/TipForm/TipForm.test.tsx | 4 +- .../validation/async-validation-manager.ts | 9 +- 6 files changed, 111 insertions(+), 12 deletions(-) create mode 100755 .husky/pre-push diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..5f26dc45 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,3 @@ +#!/bin/sh +command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; } +git lfs pre-push "$@" diff --git a/package-lock.json b/package-lock.json index ac61b891..f17ad026 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12094,6 +12094,96 @@ "optional": true } } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.1.tgz", + "integrity": "sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.1.tgz", + "integrity": "sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.1.tgz", + "integrity": "sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.1.tgz", + "integrity": "sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.1.tgz", + "integrity": "sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.1.tgz", + "integrity": "sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/src/app/components/social/GroupDiscussionThread.tsx b/src/app/components/social/GroupDiscussionThread.tsx index 4ad1eb49..de92ff7d 100644 --- a/src/app/components/social/GroupDiscussionThread.tsx +++ b/src/app/components/social/GroupDiscussionThread.tsx @@ -40,7 +40,10 @@ export default function GroupDiscussionThread({ messages, onPost }: GroupDiscuss // Auto-scroll to bottom when new messages arrive useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + const end = messagesEndRef.current; + if (typeof end?.scrollIntoView === 'function') { + end.scrollIntoView({ behavior: 'smooth' }); + } }, [messages]); const handlePost = () => { diff --git a/src/app/hooks/useStudyGroups.tsx b/src/app/hooks/useStudyGroups.tsx index 94fd8609..ccb2a5b1 100644 --- a/src/app/hooks/useStudyGroups.tsx +++ b/src/app/hooks/useStudyGroups.tsx @@ -406,7 +406,8 @@ export function useStudyGroups(currentUser?: { id: string; name: string }): UseS const groupMessages = useCallback( (groupId) => { - return messages + const persistedMessages = load(STORAGE_KEYS.messages, [] as GroupMessage[]); + return persistedMessages .filter((m) => m.groupId === groupId) .sort((a, b) => a.createdAt.localeCompare(b.createdAt)); }, @@ -415,7 +416,8 @@ export function useStudyGroups(currentUser?: { id: string; name: string }): UseS const groupResources = useCallback( (groupId) => { - return resources + const persistedResources = load(STORAGE_KEYS.resources, [] as GroupResource[]); + return persistedResources .filter((r) => r.groupId === groupId) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); }, @@ -424,7 +426,8 @@ export function useStudyGroups(currentUser?: { id: string; name: string }): UseS const groupChallenges = useCallback( (groupId) => { - return challenges + const persistedChallenges = load(STORAGE_KEYS.challenges, [] as GroupChallenge[]); + return persistedChallenges .filter((c) => c.groupId === groupId) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); }, @@ -433,7 +436,8 @@ export function useStudyGroups(currentUser?: { id: string; name: string }): UseS const challengeLeaderboard = useCallback( (challengeId) => { - const ch = challenges.find((c) => c.id === challengeId); + const persistedChallenges = load(STORAGE_KEYS.challenges, [] as GroupChallenge[]); + const ch = persistedChallenges.find((c) => c.id === challengeId); if (!ch) return []; return [...ch.progress] .sort((a, b) => b.progress - a.progress) diff --git a/src/components/tipping/TipForm/TipForm.test.tsx b/src/components/tipping/TipForm/TipForm.test.tsx index 35ea4d80..2f80066b 100644 --- a/src/components/tipping/TipForm/TipForm.test.tsx +++ b/src/components/tipping/TipForm/TipForm.test.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, waitFor } from '@/testing/utils/render'; -import { createMockUser, asyncMock, asyncErrorMock } from '@/testing/utils/mocks'; +import { render, screen, waitFor } from '../../../testing/utils/render'; +import { createMockUser, asyncMock, asyncErrorMock } from '../../../testing/utils/mocks'; // ── Service mock ────────────────────────────────────────────────────────── const mockSendTip = vi.fn(); diff --git a/src/form-management/validation/async-validation-manager.ts b/src/form-management/validation/async-validation-manager.ts index 14712dda..f42825d9 100644 --- a/src/form-management/validation/async-validation-manager.ts +++ b/src/form-management/validation/async-validation-manager.ts @@ -103,8 +103,7 @@ export class AsyncValidationManager { return existingValidation; } - // Create debounced validation - return new Promise((resolve, reject) => { + const validationPromise = new Promise((resolve, reject) => { const timer = setTimeout(async () => { this.debounceTimers.delete(fieldId); @@ -124,6 +123,9 @@ export class AsyncValidationManager { this.debounceTimers.set(fieldId, timer); }); + + this.pendingValidations.set(fieldId, validationPromise); + return validationPromise; } /** @@ -150,9 +152,6 @@ export class AsyncValidationManager { options, ); - // Store pending validation - this.pendingValidations.set(fieldId, validationPromise); - try { const result = await validationPromise;