From 1f2a52903dc6df5a10a8a39d1a657243b4944f90 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 00:50:45 +0500 Subject: [PATCH 1/9] Add unit tests for AdminUserServiceImpl, LibraryServiceImpl, and SubscriptionServiceImpl; implement Jest configuration and hooks tests --- .github/workflows/ci.yml | 217 ++ .github/workflows/deploy.yml | 80 + backend/build.gradle.kts | 25 + .../AdminLibraryControllerTest.java | 147 + .../controller/AuthControllerTest.java | 221 ++ .../stackscout/repository/RepositoryTest.java | 205 ++ .../impl/AdminUserServiceImplTest.java | 222 ++ .../service/impl/LibraryServiceImplTest.java | 248 ++ .../impl/SubscriptionServiceImplTest.java | 243 ++ frontend/jest.config.ts | 34 + frontend/jest.setup.ts | 1 + frontend/package.json | 12 +- frontend/pnpm-lock.yaml | 2742 ++++++++++++++++- .../__tests__/useLibrarySubscription.test.ts | 137 + .../hooks/__tests__/useLibraryUpdates.test.ts | 179 ++ .../__tests__/useUserSubscriptions.test.ts | 127 + infrastructure/docker/postgres/init.sql | 249 +- 17 files changed, 4921 insertions(+), 168 deletions(-) create mode 100644 backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java create mode 100644 backend/src/test/java/com/stackscout/controller/AuthControllerTest.java create mode 100644 backend/src/test/java/com/stackscout/repository/RepositoryTest.java create mode 100644 backend/src/test/java/com/stackscout/service/impl/AdminUserServiceImplTest.java create mode 100644 backend/src/test/java/com/stackscout/service/impl/LibraryServiceImplTest.java create mode 100644 backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java create mode 100644 frontend/jest.config.ts create mode 100644 frontend/jest.setup.ts create mode 100644 frontend/src/lib/hooks/__tests__/useLibrarySubscription.test.ts create mode 100644 frontend/src/lib/hooks/__tests__/useLibraryUpdates.test.ts create mode 100644 frontend/src/lib/hooks/__tests__/useUserSubscriptions.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e69de29..dab77ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -0,0 +1,217 @@ +name: CI Pipeline + +on: + push: + branches: [dev_s, develop] + pull_request: + branches: [dev_s, develop] + +env: + JAVA_VERSION: '21' + NODE_VERSION: '20' + +jobs: + # ──────────────── BACKEND ──────────────── + backend-lint: + name: Backend - Lint & Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: gradle + + - name: Grant execute permission for Gradle + run: chmod +x backend/gradlew + + - name: Run Spotless Check + working-directory: backend + run: ./gradlew spotlessCheck --continue || echo "Spotless not configured, skipping" + + backend-build: + name: Backend - Build & Test + needs: backend-lint + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_DB: stackscout_test + POSTGRES_USER: test + POSTGRES_PASSWORD: test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + redis: + image: redis:7-alpine + ports: + - 6379:6379 + rabbitmq: + image: rabbitmq:3-management-alpine + ports: + - 5672:5672 + options: >- + --health-cmd "rabbitmq-diagnostics check_port_connectivity" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/stackscout_test + SPRING_DATASOURCE_USERNAME: test + SPRING_DATASOURCE_PASSWORD: test + SPRING_REDIS_HOST: localhost + SPRING_REDIS_PORT: 6379 + SPRING_RABBITMQ_HOST: localhost + SPRING_RABBITMQ_PORT: 5672 + SPRING_RABBITMQ_USERNAME: guest + SPRING_RABBITMQ_PASSWORD: guest + JWT_SECRET: test-secret-key-for-ci-pipeline-must-be-at-least-256-bits-long + JWT_EXPIRATION: 3600000 + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: gradle + + - name: Grant execute permission for Gradle + run: chmod +x backend/gradlew + + - name: Build Backend + working-directory: backend + run: ./gradlew build -x test + + - name: Run Backend Tests + working-directory: backend + run: ./gradlew test + + - name: Generate JaCoCo Coverage Report + working-directory: backend + run: ./gradlew jacocoTestReport + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: backend-test-results + path: backend/build/reports/tests/test/ + + - name: Upload JaCoCo Coverage Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: backend-coverage-report + path: backend/build/reports/jacoco/test/ + + - name: Check Coverage Threshold + working-directory: backend + run: ./gradlew jacocoTestCoverageVerification || echo "Coverage below threshold" + + # ──────────────── FRONTEND ──────────────── + frontend-lint: + name: Frontend - Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Install Dependencies + working-directory: frontend + run: pnpm install --frozen-lockfile + + - name: Run ESLint + working-directory: frontend + run: pnpm lint + + frontend-build: + name: Frontend - Build + needs: frontend-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Install Dependencies + working-directory: frontend + run: pnpm install --frozen-lockfile + + - name: Build Frontend + working-directory: frontend + run: pnpm build + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v4 + with: + name: frontend-build-output + path: frontend/.next/ + + # ──────────────── DOCKER ──────────────── + docker-build: + name: Docker - Build Images + needs: [backend-build, frontend-build] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/dev_s' + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Backend Docker Image + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: false + load: true + tags: stackscout-backend:ci + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build Frontend Docker Image + uses: docker/build-push-action@v5 + with: + context: ./frontend + file: frontend/Dockerfile + push: false + load: true + tags: stackscout-frontend:ci + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Verify Docker Images + run: | + docker images + echo "Backend image built successfully" + echo "Frontend image built successfully" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e69de29..1d8cf9b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -0,0 +1,80 @@ +name: Deploy + +on: + workflow_run: + workflows: ["CI Pipeline"] + types: + - completed + branches: [dev_s] + +env: + REGISTRY: ghcr.io + IMAGE_NAME_BACKEND: ${{ github.repository }}/backend + IMAGE_NAME_FRONTEND: ${{ github.repository }}/frontend + +jobs: + deploy: + name: Build & Push Docker Images + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (Backend) + id: meta-backend + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BACKEND }} + tags: | + type=sha + type=ref,event=branch + latest + + - name: Build and push Backend image + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + tags: ${{ steps.meta-backend.outputs.tags }} + labels: ${{ steps.meta-backend.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Extract metadata (Frontend) + id: meta-frontend + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_FRONTEND }} + tags: | + type=sha + type=ref,event=branch + latest + + - name: Build and push Frontend image + uses: docker/build-push-action@v5 + with: + context: ./frontend + file: frontend/Dockerfile + push: true + tags: ${{ steps.meta-frontend.outputs.tags }} + labels: ${{ steps.meta-frontend.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Deployment Summary + run: | + echo "✅ Docker images pushed to GitHub Container Registry" + echo "Backend: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BACKEND }}:latest" + echo "Frontend: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_FRONTEND }}:latest" diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 66eca57..97ac62b 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -2,6 +2,7 @@ plugins { java id("org.springframework.boot") version "3.5.13" id("io.spring.dependency-management") version "1.1.7" + id("jacoco") } group = "com.stackscout" @@ -73,4 +74,28 @@ dependencies { tasks.withType { useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required = true + html.required = true + csv.required = false + } +} + +tasks.jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = "0.40".toBigDecimal() + } + } + } +} + +jacoco { + toolVersion = "0.8.11" } \ No newline at end of file diff --git a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java new file mode 100644 index 0000000..cd236fd --- /dev/null +++ b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java @@ -0,0 +1,147 @@ +package com.stackscout.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.stackscout.dto.*; +import com.stackscout.service.LibraryService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.bean.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.TestWithSecurityContext; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Integration тесты для AdminLibraryController + */ +@WebMvcTest(AdminLibraryController.class) +class AdminLibraryControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private LibraryService libraryService; + + @Test + @TestWithSecurityContext + void getAllLibraries_ShouldReturnPagedResponse() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); + + when(libraryService.getAllLibraries(any())).thenReturn(page); + + // When & Then + mockMvc.perform(get("/api/v1/admin/libraries") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.libraries").isArray()) + .andExpect(jsonPath("$.libraries[0].name").value("requests")) + .andExpect(jsonPath("$.totalElements").value(1)); + } + + @Test + @TestWithSecurityContext + void createLibrary_ShouldReturnCreatedLibrary() throws Exception { + // Given + CreateLibraryRequest request = new CreateLibraryRequest(); + request.setName("flask"); + request.setVersion("3.0.0"); + request.setSource("pypi"); + + LibraryDto created = new LibraryDto(); + created.setId(2L); + created.setName("flask"); + created.setVersion("3.0.0"); + created.setSource("pypi"); + + when(libraryService.createLibrary(any())).thenReturn(created); + + // When & Then + mockMvc.perform(post("/api/v1/admin/libraries") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.library.name").value("flask")) + .andExpect(jsonPath("$.library.id").value(2)); + } + + @Test + @TestWithSecurityContext + void updateLibrary_ShouldReturnUpdatedLibrary() throws Exception { + // Given + UpdateLibraryRequest request = new UpdateLibraryRequest(); + request.setVersion("2.32.0"); + + LibraryDto updated = createTestLibrary(); + updated.setVersion("2.32.0"); + + when(libraryService.updateLibrary(any(), any())).thenReturn(updated); + + // When & Then + mockMvc.perform(put("/api/v1/admin/libraries/1") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.library.version").value("2.32.0")); + } + + @Test + @TestWithSecurityContext + void deleteLibrary_ShouldReturnSuccessMessage() throws Exception { + // Given + // (service method will be called) + + // When & Then + mockMvc.perform(delete("/api/v1/admin/libraries/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Библиотека успешно удалена")) + .andExpect(jsonPath("$.id").value("1")); + } + + @Test + @TestWithSecurityContext + void updateModerationStatus_ShouldReturnUpdatedLibrary() throws Exception { + // Given + UpdateLibraryModerationRequest request = new UpdateLibraryModerationRequest(); + // request.setModerationStatus("APPROVED"); + + LibraryDto updated = createTestLibrary(); + + when(libraryService.updateModerationStatus(any(), any())).thenReturn(updated); + + // When & Then + mockMvc.perform(patch("/api/v1/admin/libraries/1/moderation") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("requests")); + } + + private LibraryDto createTestLibrary() { + LibraryDto library = new LibraryDto(); + library.setId(1L); + library.setName("requests"); + library.setVersion("2.31.0"); + library.setSource("pypi"); + library.setLicense("MIT"); + library.setHealthScore(85); + library.setDescription("Python HTTP library"); + return library; + } +} diff --git a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java new file mode 100644 index 0000000..ee0150f --- /dev/null +++ b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java @@ -0,0 +1,221 @@ +package com.stackscout.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.stackscout.dto.*; +import com.stackscout.service.AuthService; +import com.stackscout.service.LibraryService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.bean.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.TestWithSecurityContext; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Integration тесты для AuthController и LibraryController + */ +@WebMvcTest(AuthController.class) +class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private AuthService authService; + + @Test + void register_ShouldReturnAuthResponse() throws Exception { + // Given + RegisterRequest request = new RegisterRequest(); + request.setUsername("newuser"); + request.setEmail("new@example.com"); + request.setPassword("password123"); + + AuthResponse response = AuthResponse.builder() + .token("jwt-token-here") + .username("newuser") + .role("USER") + .build(); + + when(authService.register(any(RegisterRequest.class))).thenReturn(response); + + // When & Then + mockMvc.perform(post("/api/v1/auth/register") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.token").value("jwt-token-here")) + .andExpect(jsonPath("$.username").value("newuser")) + .andExpect(jsonPath("$.role").value("USER")); + } + + @Test + void login_ShouldReturnAuthResponse() throws Exception { + // Given + AuthenticationRequest request = new AuthenticationRequest(); + request.setUsername("testuser"); + request.setPassword("password123"); + + AuthResponse response = AuthResponse.builder() + .token("jwt-token-here") + .username("testuser") + .role("USER") + .build(); + + when(authService.authenticate(any(AuthenticationRequest.class))).thenReturn(response); + + // When & Then + mockMvc.perform(post("/api/v1/auth/login") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.token").value("jwt-token-here")) + .andExpect(jsonPath("$.username").value("testuser")); + } +} + +@WebMvcTest(LibraryController.class) +class LibraryControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private LibraryService libraryService; + + @Test + void getAllLibraries_ShouldReturnPagedResponse() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); + + when(libraryService.getAllLibraries(any())).thenReturn(page); + + // When & Then + mockMvc.perform(get("/api/v1/libraries") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.libraries").isArray()) + .andExpect(jsonPath("$.libraries[0].name").value("requests")) + .andExpect(jsonPath("$.totalElements").value(1)) + .andExpect(jsonPath("$.currentPage").value(0)); + } + + @Test + void getLibraryById_ShouldReturnLibrary() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + when(libraryService.getLibraryById(1L)).thenReturn(library); + + // When & Then + mockMvc.perform(get("/api/v1/libraries/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("requests")) + .andExpect(jsonPath("$.source").value("pypi")); + } + + @Test + void searchLibraries_ByQuery_ShouldReturnResults() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); + + when(libraryService.searchLibraries(eq("requests"), any())).thenReturn(page); + + // When & Then + mockMvc.perform(get("/api/v1/libraries/search") + .param("query", "requests")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.libraries").isArray()) + .andExpect(jsonPath("$.libraries[0].name").value("requests")); + } + + @Test + void searchLibraries_BySource_ShouldReturnFilteredResults() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); + + when(libraryService.getLibrariesBySource(eq("pypi"), any())).thenReturn(page); + + // When & Then + mockMvc.perform(get("/api/v1/libraries/search") + .param("source", "pypi")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.libraries[0].source").value("pypi")); + } + + @Test + void getHealthyLibraries_ShouldReturnFilteredByMinScore() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + library.setHealthScore(85); + + when(libraryService.getHealthyLibraries(80)).thenReturn(List.of(library)); + + // When & Then + mockMvc.perform(get("/api/v1/libraries/healthy") + .param("minScore", "80")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value("requests")) + .andExpect(jsonPath("$[0].healthScore").value(85)); + } + + @Test + void getLibraryHealth_ShouldReturnHealthMetrics() throws Exception { + // Given + LibraryDto library = createTestLibrary(); + library.setHealthScore(85); + library.setLastRelease("2024-01-15T10:00:00"); + library.setRepository("https://github.com/psf/requests"); + library.setDescription("Python HTTP library for humans"); + library.setLicense("MIT"); + + when(libraryService.getLibraryById(1L)).thenReturn(library); + + // When & Then + mockMvc.perform(get("/api/v1/libraries/1/health")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.overallScore").isNumber()) + .andExpect(jsonPath("$.actuality.score").isNumber()) + .andExpect(jsonPath("$.repository.score").isNumber()) + .andExpect(jsonPath("$.community.score").isNumber()); + } + + private LibraryDto createTestLibrary() { + LibraryDto library = new LibraryDto(); + library.setId(1L); + library.setName("requests"); + library.setVersion("2.31.0"); + library.setSource("pypi"); + library.setLicense("MIT"); + library.setHealthScore(85); + library.setDescription("Python HTTP library"); + library.setRepository("https://github.com/psf/requests"); + return library; + } +} diff --git a/backend/src/test/java/com/stackscout/repository/RepositoryTest.java b/backend/src/test/java/com/stackscout/repository/RepositoryTest.java new file mode 100644 index 0000000..730c18a --- /dev/null +++ b/backend/src/test/java/com/stackscout/repository/RepositoryTest.java @@ -0,0 +1,205 @@ +package com.stackscout.repository; + +import com.stackscout.model.Library; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration тесты для репозиториев с использованием H2 in-memory БД + */ +@DataJpaTest +class RepositoryTest { + + @Autowired + private LibraryRepository libraryRepository; + + @Autowired + private UserRepository userRepository; + + private Library testLibrary1; + private Library testLibrary2; + + @BeforeEach + void setUp() { + libraryRepository.deleteAll(); + + testLibrary1 = new Library(); + testLibrary1.setName("requests"); + testLibrary1.setVersion("2.31.0"); + testLibrary1.setSource("pypi"); + testLibrary1.setLicense("MIT"); + testLibrary1.setHealthScore(85); + testLibrary1.setDescription("Python HTTP library for humans"); + testLibrary1.setRepository("https://github.com/psf/requests"); + + testLibrary2 = new Library(); + testLibrary2.setName("flask"); + testLibrary2.setVersion("3.0.0"); + testLibrary2.setSource("pypi"); + testLibrary2.setLicense("BSD-3-Clause"); + testLibrary2.setHealthScore(90); + testLibrary2.setDescription("A lightweight WSGI web application framework"); + testLibrary2.setRepository("https://github.com/pallets/flask"); + + libraryRepository.saveAll(List.of(testLibrary1, testLibrary2)); + } + + @Test + void findByName_ShouldReturnLibrary() { + Optional result = libraryRepository.findByName("requests"); + + assertTrue(result.isPresent()); + assertEquals("requests", result.get().getName()); + assertEquals("pypi", result.get().getSource()); + } + + @Test + void findByName_WhenNotExists_ShouldReturnEmpty() { + Optional result = libraryRepository.findByName("nonexistent"); + + assertTrue(result.isEmpty()); + } + + @Test + void findBySource_ShouldReturnLibrariesFromSameSource() { + List result = libraryRepository.findBySource("pypi"); + + assertEquals(2, result.size()); + assertTrue(result.stream().allMatch(lib -> "pypi".equals(lib.getSource()))); + } + + @Test + void findBySource_WithPagination_ShouldReturnCorrectPage() { + Pageable pageable = PageRequest.of(0, 1); + Page result = libraryRepository.findBySource("pypi", pageable); + + assertEquals(1, result.getSize()); + assertEquals(2, result.getTotalElements()); + } + + @Test + void searchByName_ShouldFindPartialMatches() { + Pageable pageable = PageRequest.of(0, 10); + Page result = libraryRepository.searchByName("request", pageable); + + assertEquals(1, result.getTotalElements()); + assertEquals("requests", result.getContent().get(0).getName()); + } + + @Test + void searchByName_WithCaseInsensitivity_ShouldMatch() { + Pageable pageable = PageRequest.of(0, 10); + Page result = libraryRepository.searchByName("REQUESTS", pageable); + + assertEquals(1, result.getTotalElements()); + } + + @Test + void searchByNameAndMinScore_ShouldFilterByScore() { + Pageable pageable = PageRequest.of(0, 10); + Page result = libraryRepository.searchByNameAndMinScore("flask", 80, pageable); + + assertEquals(1, result.getTotalElements()); + assertTrue(result.getContent().get(0).getHealthScore() >= 80); + } + + @Test + void searchByNameAndSource_ShouldFilterByBoth() { + Pageable pageable = PageRequest.of(0, 10); + Page result = libraryRepository.searchByNameAndSource("flask", "pypi", pageable); + + assertEquals(1, result.getTotalElements()); + assertEquals("flask", result.getContent().get(0).getName()); + assertEquals("pypi", result.getContent().get(0).getSource()); + } + + @Test + void findByHealthScoreGreaterThanEqual_ShouldReturnQualifiedLibraries() { + List result = libraryRepository.findByHealthScoreGreaterThanEqual(85); + + assertEquals(2, result.size()); + assertTrue(result.stream().allMatch(lib -> lib.getHealthScore() >= 85)); + } + + @Test + void findBySourceAndHealthScoreGreaterThanEqual_ShouldFilterCorrectly() { + Pageable pageable = PageRequest.of(0, 10); + Page result = libraryRepository.findBySourceAndHealthScoreGreaterThanEqual("pypi", 90, pageable); + + assertEquals(1, result.getTotalElements()); + assertEquals(90, result.getContent().get(0).getHealthScore()); + } + + @Test + void findByLicense_ShouldReturnLibrariesWithSameLicense() { + List result = libraryRepository.findByLicense("MIT"); + + assertEquals(1, result.size()); + assertEquals("requests", result.get(0).getName()); + } + + @Test + void existsByNameAndVersion_ShouldReturnTrue() { + boolean exists = libraryRepository.existsByNameAndVersion("requests", "2.31.0"); + + assertTrue(exists); + } + + @Test + void existsByNameAndVersion_WhenNotExists_ShouldReturnFalse() { + boolean exists = libraryRepository.existsByNameAndVersion("requests", "3.0.0"); + + assertFalse(exists); + } + + @Test + void findAll_ShouldReturnAllLibraries() { + List result = libraryRepository.findAll(); + + assertEquals(2, result.size()); + } + + @Test + void findById_ShouldReturnLibrary() { + Long id = testLibrary1.getId(); + assertNotNull(id); + + Optional result = libraryRepository.findById(id); + + assertTrue(result.isPresent()); + assertEquals("requests", result.get().getName()); + } + + @Test + void deleteById_ShouldRemoveLibrary() { + Long id = testLibrary1.getId(); + assertNotNull(id); + + libraryRepository.deleteById(id); + + assertFalse(libraryRepository.findById(id).isPresent()); + } + + @Test + void existsById_ShouldReturnTrueForExistingLibrary() { + Long id = testLibrary1.getId(); + assertNotNull(id); + + assertTrue(libraryRepository.existsById(id)); + } + + @Test + void existsById_ShouldReturnFalseForNonExistingLibrary() { + assertFalse(libraryRepository.existsById(999L)); + } +} diff --git a/backend/src/test/java/com/stackscout/service/impl/AdminUserServiceImplTest.java b/backend/src/test/java/com/stackscout/service/impl/AdminUserServiceImplTest.java new file mode 100644 index 0000000..65c5463 --- /dev/null +++ b/backend/src/test/java/com/stackscout/service/impl/AdminUserServiceImplTest.java @@ -0,0 +1,222 @@ +package com.stackscout.service.impl; + +import com.stackscout.dto.*; +import com.stackscout.exception.ResourceNotFoundException; +import com.stackscout.model.Role; +import com.stackscout.model.User; +import com.stackscout.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Unit тесты для AdminUserServiceImpl + */ +@ExtendWith(MockitoExtension.class) +class AdminUserServiceImplTest { + + @Mock + private UserRepository userRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @InjectMocks + private AdminUserServiceImpl adminUserService; + + private User testUser; + + @BeforeEach + void setUp() { + testUser = new User(); + testUser.setId(1L); + testUser.setUsername("admin"); + testUser.setEmail("admin@stackscout.com"); + testUser.setPassword("$2a$10$encodedpassword"); + testUser.setRole(Role.ADMIN); + testUser.setEnabled(true); + testUser.setLocked(false); + testUser.setCreatedAt(LocalDateTime.now().minusDays(30)); + testUser.setUpdatedAt(LocalDateTime.now()); + } + + @Test + void getAll_ShouldReturnPagedUsers() { + // Given + Pageable pageable = PageRequest.of(0, 10); + Page userPage = new PageImpl<>(List.of(testUser), pageable, 1); + when(userRepository.findAll(pageable)).thenReturn(userPage); + + // When + Page result = adminUserService.getAll(pageable); + + // Then + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals("admin", result.getContent().get(0).getUsername()); + verify(userRepository).findAll(pageable); + } + + @Test + void getById_WhenExists_ShouldReturnUserDto() { + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + + AdminUserDto result = adminUserService.getById(1L); + + assertNotNull(result); + assertEquals("admin", result.getUsername()); + assertEquals(Role.ADMIN, result.getRole()); + } + + @Test + void getById_WhenNotExists_ShouldThrowResourceNotFoundException() { + when(userRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, () -> adminUserService.getById(999L)); + } + + @Test + void updateUser_ShouldUpdateAllFields() { + // Given + AdminUpdateUserRequest request = new AdminUpdateUserRequest(); + request.setUsername("newadmin"); + request.setEmail("new@stackscout.com"); + request.setPassword("newpassword"); + request.setRole(Role.USER); + request.setEnabled(false); + request.setLocked(true); + + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(passwordEncoder.encode("newpassword")).thenReturn("$2a$10$newencodedpassword"); + when(userRepository.save(any(User.class))).thenReturn(testUser); + + // When + AdminUserDto result = adminUserService.updateUser(1L, request); + + // Then + assertNotNull(result); + verify(userRepository).save(testUser); + assertEquals("newadmin", testUser.getUsername()); + assertEquals("new@stackscout.com", testUser.getEmail()); + assertEquals(Role.USER, testUser.getRole()); + assertFalse(testUser.getEnabled()); + assertTrue(testUser.getLocked()); + } + + @Test + void updateUser_WithPartialFields_ShouldUpdateOnlyProvidedFields() { + // Given + AdminUpdateUserRequest request = new AdminUpdateUserRequest(); + request.setUsername("updatedusername"); + // Other fields are null + + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.save(testUser)).thenReturn(testUser); + + // When + AdminUserDto result = adminUserService.updateUser(1L, request); + + // Then + assertNotNull(result); + assertEquals("updatedusername", testUser.getUsername()); + assertEquals("admin@stackscout.com", testUser.getEmail()); // unchanged + assertEquals(Role.ADMIN, testUser.getRole()); // unchanged + } + + @Test + void updateUser_WhenNotExists_ShouldThrowResourceNotFoundException() { + AdminUpdateUserRequest request = new AdminUpdateUserRequest(); + when(userRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, () -> adminUserService.updateUser(999L, request)); + } + + @Test + void updateStatus_ShouldUpdateEnabledAndLocked() { + // Given + AdminUpdateUserStatusRequest request = new AdminUpdateUserStatusRequest(); + request.setEnabled(false); + request.setLocked(true); + + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.save(testUser)).thenReturn(testUser); + + // When + AdminUserDto result = adminUserService.updateStatus(1L, request); + + // Then + assertNotNull(result); + assertFalse(testUser.getEnabled()); + assertTrue(testUser.getLocked()); + } + + @Test + void updateStatus_WithPartialFields_ShouldUpdateOnlyProvidedFields() { + // Given + AdminUpdateUserStatusRequest request = new AdminUpdateUserStatusRequest(); + request.setEnabled(false); + // locked is null + + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.save(testUser)).thenReturn(testUser); + + // When + AdminUserDto result = adminUserService.updateStatus(1L, request); + + // Then + assertFalse(testUser.getEnabled()); + assertFalse(testUser.getLocked()); // unchanged + } + + @Test + void resetPassword_ShouldEncodeAndSaveNewPassword() { + // Given + AdminResetPasswordRequest request = new AdminResetPasswordRequest(); + request.setNewPassword("newsecurepassword"); + + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(passwordEncoder.encode("newsecurepassword")).thenReturn("$2a$10$newpasswordhash"); + when(userRepository.save(testUser)).thenReturn(testUser); + + // When + AdminUserDto result = adminUserService.resetPassword(1L, request); + + // Then + assertNotNull(result); + verify(passwordEncoder).encode("newsecurepassword"); + verify(userRepository).save(testUser); + } + + @Test + void deleteUser_WhenExists_ShouldDelete() { + when(userRepository.existsById(1L)).thenReturn(true); + doNothing().when(userRepository).deleteById(1L); + + assertDoesNotThrow(() -> adminUserService.deleteUser(1L)); + verify(userRepository).deleteById(1L); + } + + @Test + void deleteUser_WhenNotExists_ShouldThrowResourceNotFoundException() { + when(userRepository.existsById(999L)).thenReturn(false); + + assertThrows(ResourceNotFoundException.class, () -> adminUserService.deleteUser(999L)); + verify(userRepository, never()).deleteById(any()); + } +} diff --git a/backend/src/test/java/com/stackscout/service/impl/LibraryServiceImplTest.java b/backend/src/test/java/com/stackscout/service/impl/LibraryServiceImplTest.java new file mode 100644 index 0000000..e57d657 --- /dev/null +++ b/backend/src/test/java/com/stackscout/service/impl/LibraryServiceImplTest.java @@ -0,0 +1,248 @@ +package com.stackscout.service.impl; + +import com.stackscout.dto.CreateLibraryRequest; +import com.stackscout.dto.LibraryDto; +import com.stackscout.dto.UpdateLibraryRequest; +import com.stackscout.exception.ResourceNotFoundException; +import com.stackscout.mapper.LibraryMapper; +import com.stackscout.model.Library; +import com.stackscout.repository.LibraryRepository; +import com.stackscout.service.LicenseService; +import com.stackscout.service.LibraryUpdateService; +import com.stackscout.source.SourceRegistryService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Unit тесты для LibraryServiceImpl + */ +@ExtendWith(MockitoExtension.class) +class LibraryServiceImplTest { + + @Mock + private LibraryRepository libraryRepository; + + @Mock + private LibraryMapper libraryMapper; + + @Mock + private LibraryUpdateService libraryUpdateService; + + @Mock + private SourceRegistryService sourceRegistryService; + + @Mock + private LicenseService licenseService; + + @InjectMocks + private LibraryServiceImpl libraryService; + + private Library testLibrary; + private LibraryDto testLibraryDto; + + @BeforeEach + void setUp() { + testLibrary = new Library(); + testLibrary.setId(1L); + testLibrary.setName("requests"); + testLibrary.setVersion("2.31.0"); + testLibrary.setSource("pypi"); + testLibrary.setLicense("MIT"); + testLibrary.setHealthScore(85); + testLibrary.setDescription("Python HTTP library"); + + testLibraryDto = new LibraryDto(); + testLibraryDto.setId(1L); + testLibraryDto.setName("requests"); + testLibraryDto.setVersion("2.31.0"); + testLibraryDto.setSource("pypi"); + testLibraryDto.setLicense("MIT"); + testLibraryDto.setHealthScore(85); + testLibraryDto.setDescription("Python HTTP library"); + } + + @Test + void getAllLibraries_ShouldReturnPagedLibraries() { + // Given + Pageable pageable = PageRequest.of(0, 10); + Page libraryPage = new PageImpl<>(List.of(testLibrary), pageable, 1); + when(libraryRepository.findAll(pageable)).thenReturn(libraryPage); + when(libraryMapper.toDto(testLibrary)).thenReturn(testLibraryDto); + + // When + Page result = libraryService.getAllLibraries(pageable); + + // Then + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals("requests", result.getContent().get(0).getName()); + verify(libraryRepository).findAll(pageable); + verify(libraryMapper).toDto(testLibrary); + } + + @Test + void getAllLibraries_WithNullPageable_ShouldThrowException() { + assertThrows(IllegalArgumentException.class, () -> libraryService.getAllLibraries(null)); + } + + @Test + void getLibraryById_WhenExists_ShouldReturnLibraryDto() { + // Given + when(libraryRepository.findById(1L)).thenReturn(Optional.of(testLibrary)); + when(libraryMapper.toDto(testLibrary)).thenReturn(testLibraryDto); + + // When + LibraryDto result = libraryService.getLibraryById(1L); + + // Then + assertNotNull(result); + assertEquals("requests", result.getName()); + verify(libraryRepository).findById(1L); + } + + @Test + void getLibraryById_WhenNotExists_ShouldThrowResourceNotFoundException() { + when(libraryRepository.findById(999L)).thenReturn(Optional.empty()); + + assertThrows(ResourceNotFoundException.class, () -> libraryService.getLibraryById(999L)); + } + + @Test + void getLibraryById_WithNullId_ShouldThrowException() { + assertThrows(IllegalArgumentException.class, () -> libraryService.getLibraryById(null)); + } + + @Test + void createLibrary_ShouldReturnCreatedLibraryDto() { + // Given + CreateLibraryRequest request = new CreateLibraryRequest(); + request.setName("flask"); + request.setVersion("3.0.0"); + request.setSource("pypi"); + + Library mappedLibrary = new Library(); + mappedLibrary.setName("flask"); + mappedLibrary.setVersion("3.0.0"); + mappedLibrary.setSource("pypi"); + + Library savedLibrary = new Library(); + savedLibrary.setId(2L); + savedLibrary.setName("flask"); + savedLibrary.setVersion("3.0.0"); + savedLibrary.setSource("pypi"); + + LibraryDto savedDto = new LibraryDto(); + savedDto.setId(2L); + savedDto.setName("flask"); + savedDto.setVersion("3.0.0"); + savedDto.setSource("pypi"); + + when(libraryMapper.toEntity(request)).thenReturn(mappedLibrary); + when(libraryRepository.save(mappedLibrary)).thenReturn(savedLibrary); + when(libraryMapper.toDto(savedLibrary)).thenReturn(savedDto); + + // When + LibraryDto result = libraryService.createLibrary(request); + + // Then + assertNotNull(result); + assertEquals("flask", result.getName()); + assertEquals(2L, result.getId()); + verify(libraryRepository).save(mappedLibrary); + } + + @Test + void updateLibrary_WhenExists_ShouldReturnUpdatedDto() { + // Given + UpdateLibraryRequest request = new UpdateLibraryRequest(); + request.setVersion("2.32.0"); + + when(libraryRepository.findById(1L)).thenReturn(Optional.of(testLibrary)); + when(libraryRepository.save(any(Library.class))).thenReturn(testLibrary); + when(libraryMapper.toDto(testLibrary)).thenReturn(testLibraryDto); + doNothing().when(libraryMapper).updateEntityFromDto(any(Library.class), any(UpdateLibraryRequest.class)); + + // When + LibraryDto result = libraryService.updateLibrary(1L, request); + + // Then + assertNotNull(result); + verify(libraryRepository).save(testLibrary); + } + + @Test + void updateLibrary_WhenNotExists_ShouldThrowResourceNotFoundException() { + when(libraryRepository.findById(999L)).thenReturn(Optional.empty()); + UpdateLibraryRequest request = new UpdateLibraryRequest(); + + assertThrows(ResourceNotFoundException.class, () -> libraryService.updateLibrary(999L, request)); + } + + @Test + void deleteLibrary_WhenExists_ShouldSucceed() { + when(libraryRepository.existsById(1L)).thenReturn(true); + doNothing().when(libraryRepository).deleteById(1L); + + assertDoesNotThrow(() -> libraryService.deleteLibrary(1L)); + verify(libraryRepository).deleteById(1L); + } + + @Test + void deleteLibrary_WhenNotExists_ShouldThrowResourceNotFoundException() { + when(libraryRepository.existsById(999L)).thenReturn(false); + + assertThrows(ResourceNotFoundException.class, () -> libraryService.deleteLibrary(999L)); + } + + @Test + void searchLibraries_ShouldReturnPagedResults() { + // Given + String query = "request"; + Pageable pageable = PageRequest.of(0, 10); + Page libraryPage = new PageImpl<>(List.of(testLibrary), pageable, 1); + + when(libraryRepository.searchByName(query, pageable)).thenReturn(libraryPage); + when(libraryMapper.toDto(testLibrary)).thenReturn(testLibraryDto); + + // When + Page result = libraryService.searchLibraries(query, pageable); + + // Then + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + verify(libraryRepository).searchByName(query, pageable); + } + + @Test + void getLibrariesStats_ShouldReturnCorrectStats() { + // Given + when(libraryRepository.findAll()).thenReturn(List.of(testLibrary)); + when(sourceRegistryService.normalize("pypi")).thenReturn("pypi"); + + // When + Object stats = libraryService.getLibrariesStats(); + + // Then + assertNotNull(stats); + assertInstanceOf(java.util.Map.class, stats); + @SuppressWarnings("unchecked") + java.util.Map statsMap = (java.util.Map) stats; + assertEquals(1L, statsMap.get("totalLibraries")); + assertEquals(85.0, statsMap.get("averageHealthScore")); + } +} diff --git a/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java b/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java new file mode 100644 index 0000000..1df4470 --- /dev/null +++ b/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java @@ -0,0 +1,243 @@ +package com.stackscout.service.impl; + +import com.stackscout.dto.CreateSubscriptionRequest; +import com.stackscout.dto.LibrarySubscriptionDto; +import com.stackscout.dto.SubscriptionStatusDto; +import com.stackscout.dto.UpdateSubscriptionRequest; +import com.stackscout.mapper.SubscriptionMapper; +import com.stackscout.model.Library; +import com.stackscout.model.LibrarySubscription; +import com.stackscout.model.User; +import com.stackscout.repository.LibraryRepository; +import com.stackscout.repository.LibrarySubscriptionRepository; +import com.stackscout.repository.UserRepository; +import jakarta.persistence.EntityNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Unit тесты для SubscriptionServiceImpl + */ +@ExtendWith(MockitoExtension.class) +class SubscriptionServiceImplTest { + + @Mock + private LibrarySubscriptionRepository subscriptionRepository; + + @Mock + private LibraryRepository libraryRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private SubscriptionMapper subscriptionMapper; + + @InjectMocks + private SubscriptionServiceImpl subscriptionService; + + private User testUser; + private Library testLibrary; + private LibrarySubscription testSubscription; + private LibrarySubscriptionDto testSubscriptionDto; + + @BeforeEach + void setUp() { + testUser = new User(); + testUser.setId(1L); + testUser.setUsername("testuser"); + testUser.setEmail("test@example.com"); + + testLibrary = new Library(); + testLibrary.setId(1L); + testLibrary.setName("requests"); + testLibrary.setSource("pypi"); + + testSubscription = LibrarySubscription.builder() + .id(1L) + .user(testUser) + .library(testLibrary) + .notificationsEnabled(true) + .build(); + + testSubscriptionDto = new LibrarySubscriptionDto(); + testSubscriptionDto.setId(1L); + testSubscriptionDto.setLibraryId(1L); + testSubscriptionDto.setLibraryName("requests"); + testSubscriptionDto.setNotificationsEnabled(true); + } + + @Test + void subscribe_ShouldCreateSubscription() { + // Given + CreateSubscriptionRequest request = new CreateSubscriptionRequest(); + request.setLibraryId(1L); + request.setNotificationsEnabled(true); + + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(false); + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(libraryRepository.findById(1L)).thenReturn(Optional.of(testLibrary)); + when(subscriptionRepository.save(any(LibrarySubscription.class))).thenReturn(testSubscription); + when(subscriptionMapper.toDto(testSubscription)).thenReturn(testSubscriptionDto); + + // When + LibrarySubscriptionDto result = subscriptionService.subscribe(1L, request); + + // Then + assertNotNull(result); + assertEquals("requests", result.getLibraryName()); + assertTrue(result.getNotificationsEnabled()); + verify(subscriptionRepository).save(any(LibrarySubscription.class)); + } + + @Test + void subscribe_WhenAlreadySubscribed_ShouldThrowException() { + CreateSubscriptionRequest request = new CreateSubscriptionRequest(); + request.setLibraryId(1L); + + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(true); + + assertThrows(IllegalStateException.class, () -> subscriptionService.subscribe(1L, request)); + verify(subscriptionRepository, never()).save(any()); + } + + @Test + void subscribe_WhenUserNotFound_ShouldThrowEntityNotFoundException() { + CreateSubscriptionRequest request = new CreateSubscriptionRequest(); + request.setLibraryId(1L); + + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(false); + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + assertThrows(EntityNotFoundException.class, () -> subscriptionService.subscribe(1L, request)); + } + + @Test + void subscribe_WhenLibraryNotFound_ShouldThrowEntityNotFoundException() { + CreateSubscriptionRequest request = new CreateSubscriptionRequest(); + request.setLibraryId(1L); + + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(false); + when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(libraryRepository.findById(1L)).thenReturn(Optional.empty()); + + assertThrows(EntityNotFoundException.class, () -> subscriptionService.subscribe(1L, request)); + } + + @Test + void unsubscribe_ShouldDeleteSubscription() { + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(true); + doNothing().when(subscriptionRepository).deleteByUserIdAndLibraryId(1L, 1L); + + assertDoesNotThrow(() -> subscriptionService.unsubscribe(1L, 1L)); + verify(subscriptionRepository).deleteByUserIdAndLibraryId(1L, 1L); + } + + @Test + void unsubscribe_WhenSubscriptionNotFound_ShouldThrowEntityNotFoundException() { + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(false); + + assertThrows(EntityNotFoundException.class, () -> subscriptionService.unsubscribe(1L, 1L)); + } + + @Test + void getUserSubscriptions_ShouldReturnPagedSubscriptions() { + // Given + Pageable pageable = PageRequest.of(0, 10); + Page subscriptionPage = new PageImpl<>(List.of(testSubscription), pageable, 1); + + when(subscriptionRepository.findByUserId(1L, pageable)).thenReturn(subscriptionPage); + when(subscriptionMapper.toDto(testSubscription)).thenReturn(testSubscriptionDto); + + // When + Page result = subscriptionService.getUserSubscriptions(1L, pageable); + + // Then + assertNotNull(result); + assertEquals(1, result.getTotalElements()); + assertEquals("requests", result.getContent().get(0).getLibraryName()); + } + + @Test + void getSubscriptionStatus_WhenSubscribed_ShouldReturnCorrectStatus() { + // Given + when(subscriptionRepository.findByUserIdAndLibraryId(1L, 1L)).thenReturn(Optional.of(testSubscription)); + when(subscriptionRepository.countByLibraryId(1L)).thenReturn(5L); + + // When + SubscriptionStatusDto result = subscriptionService.getSubscriptionStatus(1L, 1L); + + // Then + assertTrue(result.isSubscribed()); + assertEquals(5L, result.getSubscribersCount()); + assertTrue(result.getNotificationsEnabled()); + } + + @Test + void getSubscriptionStatus_WhenNotSubscribed_ShouldReturnFalse() { + when(subscriptionRepository.findByUserIdAndLibraryId(1L, 1L)).thenReturn(Optional.empty()); + when(subscriptionRepository.countByLibraryId(1L)).thenReturn(3L); + + SubscriptionStatusDto result = subscriptionService.getSubscriptionStatus(1L, 1L); + + assertFalse(result.isSubscribed()); + assertEquals(3L, result.getSubscribersCount()); + } + + @Test + void updateSubscription_ShouldReturnUpdatedDto() { + // Given + UpdateSubscriptionRequest request = new UpdateSubscriptionRequest(); + request.setNotificationsEnabled(false); + + when(subscriptionRepository.findByUserIdAndLibraryId(1L, 1L)).thenReturn(Optional.of(testSubscription)); + when(subscriptionRepository.save(testSubscription)).thenReturn(testSubscription); + when(subscriptionMapper.toDto(testSubscription)).thenReturn(testSubscriptionDto); + + // When + LibrarySubscriptionDto result = subscriptionService.updateSubscription(1L, 1L, request); + + // Then + assertNotNull(result); + assertFalse(testSubscription.getNotificationsEnabled()); + verify(subscriptionRepository).save(testSubscription); + } + + @Test + void updateSubscription_WhenNotFound_ShouldThrowEntityNotFoundException() { + UpdateSubscriptionRequest request = new UpdateSubscriptionRequest(); + when(subscriptionRepository.findByUserIdAndLibraryId(1L, 1L)).thenReturn(Optional.empty()); + + assertThrows(EntityNotFoundException.class, () -> + subscriptionService.updateSubscription(1L, 1L, request)); + } + + @Test + void isSubscribed_ShouldReturnTrue() { + when(subscriptionRepository.existsByUserIdAndLibraryId(1L, 1L)).thenReturn(true); + + assertTrue(subscriptionService.isSubscribed(1L, 1L)); + } + + @Test + void getSubscribersCount_ShouldReturnCount() { + when(subscriptionRepository.countByLibraryId(1L)).thenReturn(10L); + + assertEquals(10L, subscriptionService.getSubscribersCount(1L)); + } +} diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts new file mode 100644 index 0000000..847b556 --- /dev/null +++ b/frontend/jest.config.ts @@ -0,0 +1,34 @@ +import type { Config } from 'jest'; +import nextJest from 'next/jest.js'; + +const createJestConfig = nextJest({ + dir: './', +}); + +const config: Config = { + testEnvironment: 'jest-environment-jsdom', + setupFilesAfterEach: ['/jest.setup.ts'], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/*.stories.{ts,tsx}', + '!src/app/**/page.tsx', + '!**/node_modules/**', + ], + coverageThreshold: { + global: { + branches: 40, + functions: 40, + lines: 40, + statements: 40, + }, + }, + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, +}; + +export default createJestConfig(config); diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/frontend/jest.setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/frontend/package.json b/frontend/package.json index ac4c323..cbcd73e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,10 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, "dependencies": { "@emotion/react": "^11.14.0", @@ -26,12 +29,19 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/react-hooks": "^8.0.1", + "@types/jest": "^30.0.0", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.1.6", + "jest": "^30.3.0", + "jest-environment-jsdom": "^30.3.0", "tailwindcss": "^4", + "ts-jest": "^29.4.9", "typescript": "^5" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 948f3bd..214a04c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 7.3.8(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mui/material-nextjs': specifier: ^7.3.7 - version: 7.3.8(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + version: 7.3.8(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(next@16.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) axios: specifier: ^1.13.4 version: 1.13.6 @@ -37,7 +37,7 @@ importers: version: 0.563.0(react@19.2.3) next: specifier: 16.1.6 - version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 16.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: 19.2.3 version: 19.2.3 @@ -54,6 +54,18 @@ importers: '@tailwindcss/postcss': specifier: ^4 version: 4.2.1 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@testing-library/react-hooks': + specifier: ^8.0.1 + version: 8.0.1(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@types/jest': + specifier: ^30.0.0 + version: 30.0.0 '@types/node': specifier: ^20 version: 20.19.35 @@ -69,19 +81,34 @@ importers: eslint-config-next: specifier: 16.1.6 version: 16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + jest: + specifier: ^30.3.0 + version: 30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0) + jest-environment-jsdom: + specifier: ^30.3.0 + version: 30.3.0 tailwindcss: specifier: ^4 version: 4.2.1 + ts-jest: + specifier: ^29.4.9 + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0))(typescript@5.9.3) typescript: specifier: ^5 version: 5.9.3 packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -116,6 +143,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -137,6 +168,97 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -153,6 +275,37 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -300,89 +453,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -407,6 +576,110 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/console@30.3.0': + resolution: {integrity: sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.3.0': + resolution: {integrity: sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/diff-sequences@30.3.0': + resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment-jsdom-abstract@30.3.0': + resolution: {integrity: sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + jsdom: '*' + peerDependenciesMeta: + canvas: + optional: true + + '@jest/environment@30.3.0': + resolution: {integrity: sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.3.0': + resolution: {integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.3.0': + resolution: {integrity: sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@30.3.0': + resolution: {integrity: sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.3.0': + resolution: {integrity: sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.3.0': + resolution: {integrity: sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.3.0': + resolution: {integrity: sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.0.1': + resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.3.0': + resolution: {integrity: sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.3.0': + resolution: {integrity: sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@30.3.0': + resolution: {integrity: sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.3.0': + resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -558,24 +831,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -605,6 +882,14 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -622,6 +907,15 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@15.3.2': + resolution: {integrity: sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -669,24 +963,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -719,9 +1017,63 @@ packages: '@tailwindcss/postcss@4.2.1': resolution: {integrity: sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react-hooks@8.0.1': + resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==} + engines: {node: '>=12'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 + react: ^16.9.0 || ^17.0.0 + react-dom: ^16.9.0 || ^17.0.0 + react-test-renderer: ^16.9.0 || ^17.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react-dom: + optional: true + react-test-renderer: + optional: true + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -752,6 +1104,21 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@30.0.0': + resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} + + '@types/jsdom@21.1.7': + resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -780,9 +1147,21 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.56.1': resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -842,6 +1221,9 @@ packages: resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -881,41 +1263,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -947,16 +1337,50 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -1018,10 +1442,35 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-jest@30.3.0: + resolution: {integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-0 + + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} + engines: {node: '>=12'} + + babel-plugin-jest-hoist@30.3.0: + resolution: {integrity: sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.3.0: + resolution: {integrity: sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-beta.1 + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1037,6 +1486,9 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} @@ -1050,6 +1502,16 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1066,6 +1528,14 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001774: resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} @@ -1073,13 +1543,35 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1108,6 +1600,13 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1158,6 +1657,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -1193,9 +1696,24 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1208,14 +1726,28 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -1223,9 +1755,19 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1233,6 +1775,10 @@ packages: resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1275,6 +1821,10 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1383,6 +1933,11 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1402,6 +1957,18 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} + engines: {node: '>= 0.8.0'} + + expect@30.3.0: + resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1418,6 +1985,9 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1438,6 +2008,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1462,6 +2036,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -1480,6 +2058,14 @@ packages: react-dom: optional: true + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1498,14 +2084,26 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -1521,6 +2119,15 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1540,6 +2147,11 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -1576,6 +2188,29 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1594,10 +2229,26 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1652,6 +2303,14 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + is-generator-function@1.1.2: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} @@ -1676,6 +2335,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1688,6 +2350,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -1718,21 +2384,194 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-changed-files@30.3.0: + resolution: {integrity: sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-circus@30.3.0: + resolution: {integrity: sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-cli@30.3.0: + resolution: {integrity: sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.3.0: + resolution: {integrity: sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.3.0: + resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.2.0: + resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.3.0: + resolution: {integrity: sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-jsdom@30.3.0: + resolution: {integrity: sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jest-environment-node@30.3.0: + resolution: {integrity: sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-haste-map@30.3.0: + resolution: {integrity: sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-leak-detector@30.3.0: + resolution: {integrity: sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.3.0: + resolution: {integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.3.0: + resolution: {integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.3.0: + resolution: {integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.3.0: + resolution: {integrity: sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.3.0: + resolution: {integrity: sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.3.0: + resolution: {integrity: sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.3.0: + resolution: {integrity: sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.3.0: + resolution: {integrity: sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.3.0: + resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.3.0: + resolution: {integrity: sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.3.0: + resolution: {integrity: sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@30.3.0: + resolution: {integrity: sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.3.0: + resolution: {integrity: sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1773,6 +2612,10 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1812,24 +2655,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -1850,10 +2697,17 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1861,6 +2715,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1869,13 +2726,30 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1892,6 +2766,14 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.4: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} @@ -1899,9 +2781,17 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + motion-dom@12.34.3: resolution: {integrity: sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==} @@ -1924,6 +2814,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next@16.1.6: resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} engines: {node: '>=20.9.0'} @@ -1949,9 +2842,23 @@ packages: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1984,6 +2891,13 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1992,14 +2906,29 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2008,10 +2937,17 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2019,6 +2955,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2034,6 +2974,14 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -2050,6 +2998,14 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@30.3.0: + resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2060,6 +3016,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2068,9 +3027,21 @@ packages: peerDependencies: react: ^19.2.3 + react-error-boundary@3.1.4: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} @@ -2104,6 +3075,10 @@ packages: react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + redux-thunk@3.1.0: resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} peerDependencies: @@ -2120,13 +3095,25 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -2144,6 +3131,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2159,6 +3149,13 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -2211,21 +3208,58 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -2249,10 +3283,30 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2277,10 +3331,21 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + tailwindcss@4.2.1: resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} @@ -2288,6 +3353,10 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -2295,16 +3364,61 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + ts-jest@29.4.9: + resolution: {integrity: sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <7' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -2315,6 +3429,18 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2343,6 +3469,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -2367,9 +3498,37 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2395,6 +3554,47 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2402,6 +3602,14 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2435,8 +3643,18 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + '@alloc/quick-lru@5.2.0': {} + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -2499,6 +3717,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -2514,6 +3734,91 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.6': {} '@babel/template@7.28.6': @@ -2539,6 +3844,28 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@0.2.3': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -2792,6 +4119,213 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jest/console@30.3.0': + dependencies: + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + chalk: 4.1.2 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + slash: 3.0.0 + + '@jest/core@30.3.0(babel-plugin-macros@3.1.0)': + dependencies: + '@jest/console': 30.3.0 + '@jest/pattern': 30.0.1 + '@jest/reporters': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.4.0 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-changed-files: 30.3.0 + jest-config: 30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0) + jest-haste-map: 30.3.0 + jest-message-util: 30.3.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.3.0 + jest-resolve-dependencies: 30.3.0 + jest-runner: 30.3.0 + jest-runtime: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 + jest-watcher: 30.3.0 + pretty-format: 30.3.0 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/diff-sequences@30.3.0': {} + + '@jest/environment-jsdom-abstract@30.3.0(jsdom@26.1.0)': + dependencies: + '@jest/environment': 30.3.0 + '@jest/fake-timers': 30.3.0 + '@jest/types': 30.3.0 + '@types/jsdom': 21.1.7 + '@types/node': 20.19.35 + jest-mock: 30.3.0 + jest-util: 30.3.0 + jsdom: 26.1.0 + + '@jest/environment@30.3.0': + dependencies: + '@jest/fake-timers': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + jest-mock: 30.3.0 + + '@jest/expect-utils@30.3.0': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/expect@30.3.0': + dependencies: + expect: 30.3.0 + jest-snapshot: 30.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@30.3.0': + dependencies: + '@jest/types': 30.3.0 + '@sinonjs/fake-timers': 15.3.2 + '@types/node': 20.19.35 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + + '@jest/get-type@30.1.0': {} + + '@jest/globals@30.3.0': + dependencies: + '@jest/environment': 30.3.0 + '@jest/expect': 30.3.0 + '@jest/types': 30.3.0 + jest-mock: 30.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 20.19.35 + jest-regex-util: 30.0.1 + + '@jest/reporters@30.3.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 20.19.35 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit-x: 0.2.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + jest-worker: 30.3.0 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.49 + + '@jest/snapshot-utils@30.3.0': + dependencies: + '@jest/types': 30.3.0 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.0.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.3.0': + dependencies: + '@jest/console': 30.3.0 + '@jest/types': 30.3.0 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@30.3.0': + dependencies: + '@jest/test-result': 30.3.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + slash: 3.0.0 + + '@jest/transform@30.3.0': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 30.3.0 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + jest-regex-util: 30.0.1 + jest-util: 30.3.0 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@30.3.0': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.19.35 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2821,11 +4355,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@mui/material-nextjs@7.3.8(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + '@mui/material-nextjs@7.3.8(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(next@16.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': dependencies: '@babel/runtime': 7.28.6 '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.3) - next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + next: 16.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 optionalDependencies: '@emotion/cache': 11.14.0 @@ -2959,6 +4493,11 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + '@popperjs/core@2.11.8': {} '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1))(react@19.2.3)': @@ -2975,6 +4514,16 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@sinclair/typebox@0.34.49': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@15.3.2': + dependencies: + '@sinonjs/commons': 3.0.1 + '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} @@ -3052,11 +4601,73 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.2.1 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react-hooks@8.0.1(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + react: 19.2.3 + react-error-boundary: 3.1.4(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.14 + react-dom: 19.2.3(react@19.2.3) + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@testing-library/dom': 10.4.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3083,6 +4694,27 @@ snapshots: '@types/estree@1.0.8': {} + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@30.0.0': + dependencies: + expect: 30.3.0 + pretty-format: 30.3.0 + + '@types/jsdom@21.1.7': + dependencies: + '@types/node': 20.19.35 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -3107,8 +4739,18 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/stack-utils@2.0.3': {} + + '@types/tough-cookie@4.0.5': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -3200,6 +4842,8 @@ snapshots: '@typescript-eslint/types': 8.56.1 eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -3265,6 +4909,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -3272,12 +4918,37 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -3369,12 +5040,64 @@ snapshots: axobject-query@4.1.0: {} + babel-jest@30.3.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/transform': 30.3.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.3.0(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.3.0: + dependencies: + '@types/babel__core': 7.20.5 + babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.28.6 cosmiconfig: 7.1.0 resolve: 1.22.11 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.3.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 30.3.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -3386,6 +5109,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -3402,6 +5129,16 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3421,6 +5158,10 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + caniuse-lite@1.0.30001774: {} chalk@4.1.2: @@ -3428,10 +5169,26 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + char-regex@1.0.2: {} + + ci-info@4.4.0: {} + + cjs-module-lexer@2.2.0: {} + client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3462,6 +5219,13 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css.escape@1.5.1: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.2.3: {} d3-array@3.2.4: @@ -3504,6 +5268,11 @@ snapshots: damerau-levenshtein@1.0.8: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -3534,8 +5303,16 @@ snapshots: decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} + + dedent@1.7.2(babel-plugin-macros@3.1.0): + optionalDependencies: + babel-plugin-macros: 3.1.0 + deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -3550,12 +5327,20 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} + detect-newline@3.1.0: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.28.6 @@ -3567,8 +5352,14 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.302: {} + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.20.0: @@ -3576,6 +5367,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@6.0.1: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -3685,6 +5478,8 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} eslint-config-next@16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3): @@ -3880,6 +5675,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -3894,6 +5691,29 @@ snapshots: eventemitter3@5.0.4: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect@30.3.0: + dependencies: + '@jest/expect-utils': 30.3.0 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -3912,6 +5732,10 @@ snapshots: dependencies: reusify: 1.1.0 + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -3926,6 +5750,11 @@ snapshots: find-root@1.1.0: {} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3944,6 +5773,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -3962,6 +5796,11 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -3979,6 +5818,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3992,11 +5833,15 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-package-type@0.1.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@6.0.1: {} + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 @@ -4015,6 +5860,24 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@14.0.0: {} globals@16.4.0: {} @@ -4028,6 +5891,15 @@ snapshots: graceful-fs@4.2.11: {} + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -4060,6 +5932,32 @@ snapshots: dependencies: react-is: 16.13.1 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4073,8 +5971,22 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -4129,95 +6041,492 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-extglob@2.1.1: {} + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@30.3.0: + dependencies: + execa: 5.1.1 + jest-util: 30.3.0 + p-limit: 3.1.0 + + jest-circus@30.3.0(babel-plugin-macros@3.1.0): + dependencies: + '@jest/environment': 30.3.0 + '@jest/expect': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2(babel-plugin-macros@3.1.0) + is-generator-fn: 2.1.0 + jest-each: 30.3.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-runtime: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + p-limit: 3.1.0 + pretty-format: 30.3.0 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0): + dependencies: + '@jest/core': 30.3.0(babel-plugin-macros@3.1.0) + '@jest/test-result': 30.3.0 + '@jest/types': 30.3.0 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0) + jest-util: 30.3.0 + jest-validate: 30.3.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/get-type': 30.1.0 + '@jest/pattern': 30.0.1 + '@jest/test-sequencer': 30.3.0 + '@jest/types': 30.3.0 + babel-jest: 30.3.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 4.4.0 + deepmerge: 4.3.1 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-circus: 30.3.0(babel-plugin-macros@3.1.0) + jest-docblock: 30.2.0 + jest-environment-node: 30.3.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.3.0 + jest-runner: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 + parse-json: 5.2.0 + pretty-format: 30.3.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.19.35 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.3.0: + dependencies: + '@jest/diff-sequences': 30.3.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.3.0 + + jest-docblock@30.2.0: + dependencies: + detect-newline: 3.1.0 - is-finalizationregistry@1.1.1: + jest-each@30.3.0: dependencies: - call-bound: 1.0.4 + '@jest/get-type': 30.1.0 + '@jest/types': 30.3.0 + chalk: 4.1.2 + jest-util: 30.3.0 + pretty-format: 30.3.0 - is-generator-function@1.1.2: + jest-environment-jsdom@30.3.0: dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 + '@jest/environment': 30.3.0 + '@jest/environment-jsdom-abstract': 30.3.0(jsdom@26.1.0) + jsdom: 26.1.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate - is-glob@4.0.3: + jest-environment-node@30.3.0: dependencies: - is-extglob: 2.1.1 + '@jest/environment': 30.3.0 + '@jest/fake-timers': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + jest-mock: 30.3.0 + jest-util: 30.3.0 + jest-validate: 30.3.0 - is-map@2.0.3: {} + jest-haste-map@30.3.0: + dependencies: + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.3.0 + jest-worker: 30.3.0 + picomatch: 4.0.3 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 - is-negative-zero@2.0.3: {} + jest-leak-detector@30.3.0: + dependencies: + '@jest/get-type': 30.1.0 + pretty-format: 30.3.0 - is-number-object@1.1.1: + jest-matcher-utils@30.3.0: dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.3.0 + pretty-format: 30.3.0 - is-number@7.0.0: {} + jest-message-util@30.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 30.3.0 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + pretty-format: 30.3.0 + slash: 3.0.0 + stack-utils: 2.0.6 - is-regex@1.2.1: + jest-mock@30.3.0: dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + jest-util: 30.3.0 - is-set@2.0.3: {} + jest-pnp-resolver@1.2.3(jest-resolve@30.3.0): + optionalDependencies: + jest-resolve: 30.3.0 - is-shared-array-buffer@1.0.4: + jest-regex-util@30.0.1: {} + + jest-resolve-dependencies@30.3.0: dependencies: - call-bound: 1.0.4 + jest-regex-util: 30.0.1 + jest-snapshot: 30.3.0 + transitivePeerDependencies: + - supports-color - is-string@1.1.1: + jest-resolve@30.3.0: dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.3.0) + jest-util: 30.3.0 + jest-validate: 30.3.0 + slash: 3.0.0 + unrs-resolver: 1.11.1 - is-symbol@1.1.1: + jest-runner@30.3.0: dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 + '@jest/console': 30.3.0 + '@jest/environment': 30.3.0 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.2.0 + jest-environment-node: 30.3.0 + jest-haste-map: 30.3.0 + jest-leak-detector: 30.3.0 + jest-message-util: 30.3.0 + jest-resolve: 30.3.0 + jest-runtime: 30.3.0 + jest-util: 30.3.0 + jest-watcher: 30.3.0 + jest-worker: 30.3.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color - is-typed-array@1.1.15: + jest-runtime@30.3.0: dependencies: - which-typed-array: 1.1.20 + '@jest/environment': 30.3.0 + '@jest/fake-timers': 30.3.0 + '@jest/globals': 30.3.0 + '@jest/source-map': 30.0.1 + '@jest/test-result': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + chalk: 4.1.2 + cjs-module-lexer: 2.2.0 + collect-v8-coverage: 1.0.3 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.3.0 + jest-snapshot: 30.3.0 + jest-util: 30.3.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color - is-weakmap@2.0.2: {} + jest-snapshot@30.3.0: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 30.3.0 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.3.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 30.3.0 + graceful-fs: 4.2.11 + jest-diff: 30.3.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-util: 30.3.0 + pretty-format: 30.3.0 + semver: 7.7.4 + synckit: 0.11.12 + transitivePeerDependencies: + - supports-color - is-weakref@1.1.1: + jest-util@30.3.0: dependencies: - call-bound: 1.0.4 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.3 - is-weakset@2.0.4: + jest-validate@30.3.0: dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 + '@jest/get-type': 30.1.0 + '@jest/types': 30.3.0 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.3.0 - isarray@2.0.5: {} + jest-watcher@30.3.0: + dependencies: + '@jest/test-result': 30.3.0 + '@jest/types': 30.3.0 + '@types/node': 20.19.35 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.3.0 + string-length: 4.0.2 - isexe@2.0.0: {} + jest-worker@30.3.0: + dependencies: + '@types/node': 20.19.35 + '@ungap/structured-clone': 1.3.0 + jest-util: 30.3.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 - iterator.prototype@1.1.5: + jest@30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0): dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 + '@jest/core': 30.3.0(babel-plugin-macros@3.1.0) + '@jest/types': 30.3.0 + import-local: 3.2.0 + jest-cli: 30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node jiti@2.6.1: {} js-tokens@4.0.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.1: dependencies: argparse: 2.0.1 + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.20.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -4251,6 +6560,8 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + leven@3.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -4307,16 +6618,24 @@ snapshots: lines-and-columns@1.2.4: {} + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4325,12 +6644,26 @@ snapshots: dependencies: react: 19.2.3 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + math-intrinsics@1.1.0: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -4344,6 +6677,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + minimatch@10.2.4: dependencies: brace-expansion: 5.0.4 @@ -4352,8 +6689,14 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.0 + minimist@1.2.8: {} + minipass@7.1.3: {} + motion-dom@12.34.3: dependencies: motion-utils: 12.29.2 @@ -4368,7 +6711,9 @@ snapshots: natural-compare@1.4.0: {} - next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + neo-async@2.6.2: {} + + next@16.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 @@ -4377,7 +6722,7 @@ snapshots: postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.3) + styled-jsx: 5.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react@19.2.3) optionalDependencies: '@next/swc-darwin-arm64': 16.1.6 '@next/swc-darwin-x64': 16.1.6 @@ -4399,8 +6744,18 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 + node-int64@0.4.0: {} + node-releases@2.0.27: {} + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nwsapi@2.2.23: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4443,6 +6798,14 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4458,14 +6821,26 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4477,12 +6852,23 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + path-type@4.0.0: {} picocolors@1.1.1: {} @@ -4491,6 +6877,12 @@ snapshots: picomatch@4.0.3: {} + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + possible-typed-array-names@1.1.0: {} postcss@8.4.31: @@ -4507,6 +6899,18 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@30.3.0: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -4517,6 +6921,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@7.0.1: {} + queue-microtask@1.2.3: {} react-dom@19.2.3(react@19.2.3): @@ -4524,8 +6930,17 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 + react-error-boundary@3.1.4(react@19.2.3): + dependencies: + '@babel/runtime': 7.28.6 + react: 19.2.3 + react-is@16.13.1: {} + react-is@17.0.2: {} + + react-is@18.3.1: {} + react-is@19.2.4: {} react-redux@9.2.0(@types/react@19.2.14)(react@19.2.3)(redux@5.0.1): @@ -4568,6 +6983,11 @@ snapshots: - '@types/react' - redux + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + redux-thunk@3.1.0(redux@5.0.1): dependencies: redux: 5.0.1 @@ -4594,10 +7014,18 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-directory@2.1.1: {} + reselect@5.1.1: {} + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.11: @@ -4617,6 +7045,8 @@ snapshots: reusify@1.1.0: {} + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -4640,6 +7070,12 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -4734,17 +7170,53 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map@0.5.7: {} + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + stable-hash@0.0.5: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -4795,16 +7267,33 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.3): + styled-jsx@5.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react@19.2.3): dependencies: client-only: 0.0.1 react: 19.2.3 optionalDependencies: '@babel/core': 7.29.0 + babel-plugin-macros: 3.1.0 stylis@4.2.0: {} @@ -4812,12 +7301,28 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + tailwindcss@4.2.1: {} tapable@2.3.0: {} + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 7.2.3 + minimatch: 3.1.5 + tiny-invariant@1.3.3: {} tinyglobby@0.2.15: @@ -4825,14 +7330,50 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 + ts-jest@29.4.9(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(jest-util@30.3.0)(jest@30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.9 + jest: 30.3.0(@types/node@20.19.35)(babel-plugin-macros@3.1.0) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.4 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.0 + '@jest/transform': 30.3.0 + '@jest/types': 30.3.0 + babel-jest: 30.3.0(@babel/core@7.29.0) + jest-util: 30.3.0 + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -4846,6 +7387,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -4892,6 +7439,9 @@ snapshots: typescript@5.9.3: {} + uglify-js@3.19.3: + optional: true + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -4939,6 +7489,12 @@ snapshots: dependencies: react: 19.2.3 + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -4956,6 +7512,27 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -5003,10 +7580,51 @@ snapshots: word-wrap@1.2.5: {} + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + ws@8.20.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + yallist@3.1.1: {} yaml@1.10.2: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.3.6): diff --git a/frontend/src/lib/hooks/__tests__/useLibrarySubscription.test.ts b/frontend/src/lib/hooks/__tests__/useLibrarySubscription.test.ts new file mode 100644 index 0000000..6429b9a --- /dev/null +++ b/frontend/src/lib/hooks/__tests__/useLibrarySubscription.test.ts @@ -0,0 +1,137 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { useLibrarySubscription } from '@/lib/hooks/useLibrarySubscription'; +import { subscriptionApi } from '@/lib/api'; + +// Mock the API module +jest.mock('@/lib/api', () => ({ + subscriptionApi: { + subscribe: jest.fn(), + unsubscribe: jest.fn(), + getSubscriptionStatus: jest.fn(), + updateNotifications: jest.fn(), + }, +})); + +describe('useLibrarySubscription', () => { + const libraryId = 1; + const mockSubscription = { + id: 1, + userId: 1, + libraryId: 1, + libraryName: 'Test Library', + librarySource: 'pypi', + subscribedAt: '2024-01-01T00:00:00Z', + notificationsEnabled: true, + }; + + const mockStatus = { + isSubscribed: false, + subscribersCount: 10, + notificationsEnabled: false, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize with default values', () => { + const { result } = renderHook(() => useLibrarySubscription(libraryId)); + + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + expect(result.current.subscription).toBeNull(); + expect(result.current.status).toBeNull(); + }); + + it('should successfully subscribe to a library', async () => { + (subscriptionApi.subscribe as jest.Mock).mockResolvedValue({ + data: { subscription: mockSubscription }, + }); + (subscriptionApi.getSubscriptionStatus as jest.Mock).mockResolvedValue({ + data: { ...mockStatus, isSubscribed: true }, + }); + + const { result } = renderHook(() => useLibrarySubscription(libraryId)); + + await act(async () => { + await result.current.subscribe(); + }); + + await waitFor(() => { + expect(subscriptionApi.subscribe).toHaveBeenCalledWith(libraryId); + }); + }); + + it('should successfully unsubscribe from a library', async () => { + (subscriptionApi.unsubscribe as jest.Mock).mockResolvedValue({ data: { success: true } }); + (subscriptionApi.getSubscriptionStatus as jest.Mock).mockResolvedValue({ + data: { ...mockStatus, isSubscribed: false }, + }); + + const { result } = renderHook(() => useLibrarySubscription(libraryId)); + + await act(async () => { + await result.current.unsubscribe(); + }); + + await waitFor(() => { + expect(subscriptionApi.unsubscribe).toHaveBeenCalledWith(libraryId); + }); + }); + + it('should fetch subscription status', async () => { + (subscriptionApi.getSubscriptionStatus as jest.Mock).mockResolvedValue({ + data: mockStatus, + }); + + const { result } = renderHook(() => useLibrarySubscription(libraryId)); + + await act(async () => { + await result.current.fetchSubscriptionStatus(); + }); + + await waitFor(() => { + expect(result.current.status).toEqual(mockStatus); + expect(subscriptionApi.getSubscriptionStatus).toHaveBeenCalledWith(libraryId); + }); + }); + + it('should toggle notifications', async () => { + const updatedSubscription = { ...mockSubscription, notificationsEnabled: false }; + (subscriptionApi.updateNotifications as jest.Mock).mockResolvedValue({ + data: updatedSubscription, + }); + + const { result } = renderHook(() => useLibrarySubscription(libraryId)); + + await act(async () => { + await result.current.toggleNotifications(false); + }); + + await waitFor(() => { + expect(subscriptionApi.updateNotifications).toHaveBeenCalledWith(libraryId, false); + }); + }); + + it('should handle subscription error', async () => { + const errorMessage = 'Failed to subscribe'; + (subscriptionApi.subscribe as jest.Mock).mockRejectedValue({ + response: { data: { message: errorMessage } }, + }); + + const { result } = renderHook(() => useLibrarySubscription(libraryId)); + + await act(async () => { + try { + await result.current.subscribe(); + } catch { + // Expected error + } + }); + + await waitFor(() => { + expect(result.current.error).toBe(errorMessage); + expect(result.current.isLoading).toBe(false); + }); + }); +}); diff --git a/frontend/src/lib/hooks/__tests__/useLibraryUpdates.test.ts b/frontend/src/lib/hooks/__tests__/useLibraryUpdates.test.ts new file mode 100644 index 0000000..bb534df --- /dev/null +++ b/frontend/src/lib/hooks/__tests__/useLibraryUpdates.test.ts @@ -0,0 +1,179 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { useLibraryUpdates, useUpdateStats } from '@/lib/hooks/useLibraryUpdates'; +import { libraryUpdateApi } from '@/lib/api'; + +jest.mock('@/lib/api', () => ({ + libraryUpdateApi: { + getUpdatesForUser: jest.fn(), + getRecentUpdates: jest.fn(), + getUpdateStats: jest.fn(), + }, +})); + +describe('useLibraryUpdates', () => { + const mockUpdates = { + content: [ + { + id: 1, + libraryId: 1, + libraryName: 'requests', + librarySource: 'pypi', + oldVersion: '2.30.0', + newVersion: '2.31.0', + updateType: 'MINOR' as const, + updateDate: '2024-01-15T10:00:00Z', + }, + { + id: 2, + libraryId: 2, + libraryName: 'flask', + librarySource: 'pypi', + oldVersion: '2.9.0', + newVersion: '3.0.0', + updateType: 'MAJOR' as const, + updateDate: '2024-01-14T08:00:00Z', + }, + ], + totalElements: 2, + totalPages: 1, + number: 0, + size: 20, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize with default values', () => { + const { result } = renderHook(() => useLibraryUpdates()); + + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + expect(result.current.updates).toHaveLength(0); + expect(result.current.pagination).toEqual({ + totalElements: 0, + totalPages: 0, + currentPage: 0, + size: 20, + }); + }); + + it('should fetch updates successfully', async () => { + (libraryUpdateApi.getUpdatesForUser as jest.Mock).mockResolvedValue({ + data: mockUpdates, + }); + + const { result } = renderHook(() => useLibraryUpdates()); + + await act(async () => { + await result.current.fetchUpdates(0, 20); + }); + + await waitFor(() => { + expect(result.current.updates).toEqual(mockUpdates.content); + expect(result.current.pagination.totalPages).toBe(1); + expect(result.current.pagination.totalElements).toBe(2); + }); + + expect(libraryUpdateApi.getUpdatesForUser).toHaveBeenCalledWith(0, 20); + }); + + it('should fetch recent updates', async () => { + const recentUpdates = mockUpdates.content.slice(0, 1); + (libraryUpdateApi.getRecentUpdates as jest.Mock).mockResolvedValue({ + data: recentUpdates, + }); + + const { result } = renderHook(() => useLibraryUpdates()); + + await act(async () => { + await result.current.fetchRecentUpdates(7); + }); + + await waitFor(() => { + expect(result.current.updates).toEqual(recentUpdates); + }); + + expect(libraryUpdateApi.getRecentUpdates).toHaveBeenCalledWith(7); + }); + + it('should handle fetch error', async () => { + const errorMessage = 'Failed to fetch updates'; + (libraryUpdateApi.getUpdatesForUser as jest.Mock).mockRejectedValue({ + response: { data: { message: errorMessage } }, + }); + + const { result } = renderHook(() => useLibraryUpdates()); + + await act(async () => { + const res = await result.current.fetchUpdates(0, 20); + expect(res).toBeNull(); + }); + + await waitFor(() => { + expect(result.current.error).toBe(errorMessage); + expect(result.current.isLoading).toBe(false); + }); + }); + + it('should auto-fetch when autoFetch is true', async () => { + (libraryUpdateApi.getUpdatesForUser as jest.Mock).mockResolvedValue({ + data: mockUpdates, + }); + + renderHook(() => useLibraryUpdates({ autoFetch: true, page: 0, size: 20 })); + + await waitFor(() => { + expect(libraryUpdateApi.getUpdatesForUser).toHaveBeenCalledWith(0, 20); + }); + }); +}); + +describe('useUpdateStats', () => { + const mockStats = { + last7Days: 5, + last30Days: 20, + recentUpdates: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch stats successfully', async () => { + (libraryUpdateApi.getUpdateStats as jest.Mock).mockResolvedValue({ + data: mockStats, + }); + + const { result } = renderHook(() => useUpdateStats()); + + await act(async () => { + await result.current.fetchStats(); + }); + + await waitFor(() => { + expect(result.current.stats).toEqual(mockStats); + expect(result.current.isLoading).toBe(false); + }); + + expect(libraryUpdateApi.getUpdateStats).toHaveBeenCalled(); + }); + + it('should handle stats fetch error', async () => { + const errorMessage = 'Failed to fetch stats'; + (libraryUpdateApi.getUpdateStats as jest.Mock).mockRejectedValue({ + response: { data: { message: errorMessage } }, + }); + + const { result } = renderHook(() => useUpdateStats()); + + await act(async () => { + const res = await result.current.fetchStats(); + expect(res).toBeNull(); + }); + + await waitFor(() => { + expect(result.current.error).toBe(errorMessage); + }); + }); +}); diff --git a/frontend/src/lib/hooks/__tests__/useUserSubscriptions.test.ts b/frontend/src/lib/hooks/__tests__/useUserSubscriptions.test.ts new file mode 100644 index 0000000..91c077e --- /dev/null +++ b/frontend/src/lib/hooks/__tests__/useUserSubscriptions.test.ts @@ -0,0 +1,127 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { useUserSubscriptions } from '@/lib/hooks/useUserSubscriptions'; +import { subscriptionApi } from '@/lib/api'; + +jest.mock('@/lib/api', () => ({ + subscriptionApi: { + getUserSubscriptions: jest.fn(), + }, +})); + +describe('useUserSubscriptions', () => { + const mockSubscriptions = { + content: [ + { + id: 1, + userId: 1, + libraryId: 1, + libraryName: 'requests', + librarySource: 'pypi', + subscribedAt: '2024-01-01T00:00:00Z', + notificationsEnabled: true, + }, + { + id: 2, + userId: 1, + libraryId: 2, + libraryName: 'flask', + librarySource: 'pypi', + subscribedAt: '2024-01-02T00:00:00Z', + notificationsEnabled: false, + }, + ], + totalElements: 2, + totalPages: 1, + number: 0, + size: 20, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize with default values', () => { + const { result } = renderHook(() => useUserSubscriptions()); + + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + expect(result.current.subscriptions).toHaveLength(0); + expect(result.current.pagination).toEqual({ + totalElements: 0, + totalPages: 0, + currentPage: 0, + size: 20, + }); + }); + + it('should fetch user subscriptions successfully', async () => { + (subscriptionApi.getUserSubscriptions as jest.Mock).mockResolvedValue({ + data: mockSubscriptions, + }); + + const { result } = renderHook(() => useUserSubscriptions()); + + await act(async () => { + await result.current.fetchSubscriptions(0, 20); + }); + + await waitFor(() => { + expect(result.current.subscriptions).toEqual(mockSubscriptions.content); + expect(result.current.pagination.totalPages).toBe(1); + expect(result.current.pagination.totalElements).toBe(2); + }); + + expect(subscriptionApi.getUserSubscriptions).toHaveBeenCalledWith(0, 20, 'subscribedAt', 'DESC'); + }); + + it('should handle fetch error', async () => { + const errorMessage = 'Failed to fetch subscriptions'; + (subscriptionApi.getUserSubscriptions as jest.Mock).mockRejectedValue({ + response: { data: { message: errorMessage } }, + }); + + const { result } = renderHook(() => useUserSubscriptions()); + + await act(async () => { + const res = await result.current.fetchSubscriptions(0, 20); + expect(res).toBeNull(); + }); + + await waitFor(() => { + expect(result.current.error).toBe(errorMessage); + expect(result.current.isLoading).toBe(false); + }); + }); + + it('should check if subscribed to specific library', async () => { + (subscriptionApi.getUserSubscriptions as jest.Mock).mockResolvedValue({ + data: mockSubscriptions, + }); + + const { result } = renderHook(() => useUserSubscriptions()); + + await act(async () => { + await result.current.fetchSubscriptions(); + }); + + await waitFor(() => { + expect(result.current.subscriptions).toHaveLength(2); + }); + + expect(result.current.isSubscribedTo(1)).toBe(true); + expect(result.current.isSubscribedTo(2)).toBe(true); + expect(result.current.isSubscribedTo(99)).toBe(false); + }); + + it('should auto-fetch when autoFetch is true', async () => { + (subscriptionApi.getUserSubscriptions as jest.Mock).mockResolvedValue({ + data: mockSubscriptions, + }); + + renderHook(() => useUserSubscriptions({ autoFetch: true, page: 0, size: 20 })); + + await waitFor(() => { + expect(subscriptionApi.getUserSubscriptions).toHaveBeenCalledWith(0, 20, 'subscribedAt', 'DESC'); + }); + }); +}); diff --git a/infrastructure/docker/postgres/init.sql b/infrastructure/docker/postgres/init.sql index d0cab10..d3df7ac 100644 --- a/infrastructure/docker/postgres/init.sql +++ b/infrastructure/docker/postgres/init.sql @@ -1,34 +1,34 @@ -- StackScout PostgreSQL Initialization Script --- Создание базовой схемы БД и таблиц +-- Синхронизировано с Flyway миграциями (V1-V13) -- Включить необходимые расширения CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pg_trgm"; --- Создать основные таблицы -CREATE TABLE IF NOT EXISTS packages ( +-- ═══════════════════════════════════════════ +-- V1: Initial Schema (libraries) +-- ═══════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS libraries ( id BIGSERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL UNIQUE, - version VARCHAR(100) NOT NULL, + name VARCHAR(255) NOT NULL, + version VARCHAR(50) NOT NULL, + source VARCHAR(50) NOT NULL, + license_name VARCHAR(100), + health_score INTEGER, + last_release VARCHAR(50), + repository VARCHAR(500), description TEXT, - source_type VARCHAR(50) NOT NULL, - repository_url VARCHAR(500), - homepage_url VARCHAR(500), - health_score INTEGER CHECK (health_score >= 0 AND health_score <= 100), - actuality_score INTEGER, - activity_score INTEGER, - repository_score INTEGER, - community_score INTEGER, - license VARCHAR(100), - downloads_count BIGINT DEFAULT 0, - author VARCHAR(255), - maintainer VARCHAR(255), - last_update TIMESTAMP, - last_checked TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP ); +CREATE INDEX IF NOT EXISTS idx_library_name ON libraries(name); +CREATE INDEX IF NOT EXISTS idx_library_source ON libraries(source); +CREATE INDEX IF NOT EXISTS idx_library_health_score ON libraries(health_score); + +-- ═══════════════════════════════════════════ +-- V2: Licenses and compatibility +-- ═══════════════════════════════════════════ CREATE TABLE IF NOT EXISTS licenses ( id BIGSERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, @@ -38,83 +38,14 @@ CREATE TABLE IF NOT EXISTS licenses ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS projects ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - source_url VARCHAR(500), - status VARCHAR(50) DEFAULT 'ACTIVE', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS project_dependencies ( - id BIGSERIAL PRIMARY KEY, - project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, - package_id BIGINT NOT NULL REFERENCES packages(id) ON DELETE RESTRICT, - version_specified VARCHAR(100) NOT NULL, - version_locked VARCHAR(100), - status VARCHAR(50) DEFAULT 'ACTIVE', - license_compliant BOOLEAN DEFAULT TRUE, - has_vulnerabilities BOOLEAN DEFAULT FALSE, - added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - checked_at TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS package_dependencies ( - id BIGSERIAL PRIMARY KEY, - package_id BIGINT NOT NULL REFERENCES packages(id) ON DELETE CASCADE, - dependency_id BIGINT NOT NULL REFERENCES packages(id) ON DELETE RESTRICT, - version_range VARCHAR(100), - is_optional BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS vulnerabilities ( +CREATE TABLE IF NOT EXISTS license_compatibility ( id BIGSERIAL PRIMARY KEY, - package_id BIGINT NOT NULL REFERENCES packages(id) ON DELETE CASCADE, - cve_id VARCHAR(50) UNIQUE, - severity VARCHAR(50), - description TEXT, - affected_versions_from VARCHAR(100), - affected_versions_to VARCHAR(100), - patched_versions VARCHAR(500), - source_url VARCHAR(500), - published_at TIMESTAMP, - discovered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + license_id BIGINT NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + compatible_with_id BIGINT NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, + is_compatible BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS health_check_logs ( - id BIGSERIAL PRIMARY KEY, - package_id BIGINT NOT NULL REFERENCES packages(id) ON DELETE CASCADE, - health_score INTEGER, - actuality_score INTEGER, - activity_score INTEGER, - repository_score INTEGER, - community_score INTEGER, - source VARCHAR(100), - status VARCHAR(50), - error_message TEXT, - checked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Создать индексы для оптимизации запросов -CREATE INDEX IF NOT EXISTS idx_packages_name ON packages(name); -CREATE INDEX IF NOT EXISTS idx_packages_health_score ON packages(health_score DESC); -CREATE INDEX IF NOT EXISTS idx_packages_source_type ON packages(source_type); -CREATE INDEX IF NOT EXISTS idx_packages_last_update ON packages(last_update DESC); -CREATE INDEX IF NOT EXISTS idx_licenses_classification ON licenses(classification); -CREATE INDEX IF NOT EXISTS idx_project_deps_project_id ON project_dependencies(project_id); -CREATE INDEX IF NOT EXISTS idx_project_deps_package_id ON project_dependencies(package_id); -CREATE INDEX IF NOT EXISTS idx_package_deps_package_id ON package_dependencies(package_id); -CREATE INDEX IF NOT EXISTS idx_vulnerabilities_severity ON vulnerabilities(severity); -CREATE INDEX IF NOT EXISTS idx_vulnerabilities_package_id ON vulnerabilities(package_id); -CREATE INDEX IF NOT EXISTS idx_health_check_package_id ON health_check_logs(package_id); - -- Вставить стандартные лицензии INSERT INTO licenses (name, description, classification) VALUES ('MIT', 'MIT License', 'PERMISSIVE'), @@ -131,7 +62,122 @@ INSERT INTO licenses (name, description, classification) VALUES ('CC0-1.0', 'Creative Commons Zero v1.0', 'PERMISSIVE') ON CONFLICT (name) DO NOTHING; --- Создать функцию для обновления updated_at +-- ═══════════════════════════════════════════ +-- V3: Scan jobs +-- ═══════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS scan_jobs ( + id BIGSERIAL PRIMARY KEY, + library_id BIGINT, + status VARCHAR(50) NOT NULL DEFAULT 'PENDING', + scan_type VARCHAR(50), + started_at TIMESTAMP, + completed_at TIMESTAMP, + result TEXT, + error_message TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ═══════════════════════════════════════════ +-- V4: Users +-- ═══════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(255) NOT NULL UNIQUE, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role VARCHAR(50) NOT NULL DEFAULT 'USER', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); +CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); + +-- ═══════════════════════════════════════════ +-- V5: Scraper tasks +-- ═══════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS scraper_tasks ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + source VARCHAR(50) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'PENDING', + progress INTEGER DEFAULT 0, + total_packages INTEGER DEFAULT 0, + processed_packages INTEGER DEFAULT 0, + started_at TIMESTAMP, + completed_at TIMESTAMP, + error_message TEXT, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP +); + +-- ═══════════════════════════════════════════ +-- V6: Moderation fields on libraries +-- ═══════════════════════════════════════════ +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS moderation_status VARCHAR(50) DEFAULT 'PENDING'; +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS moderation_notes TEXT; +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS moderated_by BIGINT REFERENCES users(id); +ALTER TABLE libraries ADD COLUMN IF NOT EXISTS moderated_at TIMESTAMP; + +-- ═══════════════════════════════════════════ +-- V7: Subscriptions and library updates +-- ═══════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS library_subscriptions ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + library_id BIGINT NOT NULL REFERENCES libraries(id) ON DELETE CASCADE, + notifications_enabled BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE(user_id, library_id) +); + +CREATE TABLE IF NOT EXISTS library_updates ( + id BIGSERIAL PRIMARY KEY, + library_id BIGINT NOT NULL REFERENCES libraries(id) ON DELETE CASCADE, + old_version VARCHAR(50) NOT NULL, + new_version VARCHAR(50) NOT NULL, + update_type VARCHAR(50) NOT NULL, + update_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + old_health_score INTEGER, + new_health_score INTEGER, + changelog TEXT, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON library_subscriptions(user_id); +CREATE INDEX IF NOT EXISTS idx_subscriptions_library_id ON library_subscriptions(library_id); +CREATE INDEX IF NOT EXISTS idx_updates_library_id ON library_updates(library_id); + +-- ═══════════════════════════════════════════ +-- V8: User status fields +-- ═══════════════════════════════════════════ +ALTER TABLE users ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT TRUE; +ALTER TABLE users ADD COLUMN IF NOT EXISTS locked BOOLEAN NOT NULL DEFAULT FALSE; + +-- ═══════════════════════════════════════════ +-- V9: Password reset tokens +-- ═══════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS password_reset_tokens ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token VARCHAR(255) NOT NULL UNIQUE, + expiry_date TIMESTAMP NOT NULL, + used BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_reset_tokens_token ON password_reset_tokens(token); + +-- ═══════════════════════════════════════════ +-- V10-V13: Stage 2-5 sources (additional columns) +-- ═══════════════════════════════════════════ +-- Эти миграции расширяют список источников данных +-- и не добавляют новых таблиц, только конфигурацию + +-- ═══════════════════════════════════════════ +-- Триггеры для updated_at +-- ═══════════════════════════════════════════ CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN @@ -140,18 +186,11 @@ BEGIN END; $$ LANGUAGE plpgsql; --- Применить триггеры -CREATE TRIGGER update_packages_updated_at BEFORE UPDATE ON packages - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_project_dependencies_updated_at BEFORE UPDATE ON project_dependencies +CREATE TRIGGER update_libraries_updated_at BEFORE UPDATE ON libraries FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -CREATE TRIGGER update_package_dependencies_updated_at BEFORE UPDATE ON package_dependencies +CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Коммент для отслеживания версии -COMMENT ON SCHEMA public IS 'StackScout Database Schema v1.0'; +COMMENT ON SCHEMA public IS 'StackScout Database Schema v13.0 (Synced with Flyway)'; From 27a5a08f6473e37b0bf2eaeb5995556105bf0fe8 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:04:24 +0500 Subject: [PATCH 2/9] refactor: update security context annotations in AdminLibraryControllerTest and AuthControllerTest; improve subscription status assertions in SubscriptionServiceImplTest; fix jest config setupFilesAfterEnv --- .../AdminLibraryControllerTest.java | 12 +++---- .../controller/AuthControllerTest.java | 34 ++++--------------- .../impl/SubscriptionServiceImplTest.java | 4 +-- frontend/jest.config.ts | 2 +- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java index cd236fd..1f17777 100644 --- a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java @@ -11,7 +11,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; -import org.springframework.security.test.context.TestWithSecurityContext; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import java.util.List; @@ -37,7 +37,7 @@ class AdminLibraryControllerTest { private LibraryService libraryService; @Test - @TestWithSecurityContext + @WithMockUser void getAllLibraries_ShouldReturnPagedResponse() throws Exception { // Given LibraryDto library = createTestLibrary(); @@ -56,7 +56,7 @@ void getAllLibraries_ShouldReturnPagedResponse() throws Exception { } @Test - @TestWithSecurityContext + @WithMockUser void createLibrary_ShouldReturnCreatedLibrary() throws Exception { // Given CreateLibraryRequest request = new CreateLibraryRequest(); @@ -82,7 +82,7 @@ void createLibrary_ShouldReturnCreatedLibrary() throws Exception { } @Test - @TestWithSecurityContext + @WithMockUser void updateLibrary_ShouldReturnUpdatedLibrary() throws Exception { // Given UpdateLibraryRequest request = new UpdateLibraryRequest(); @@ -102,7 +102,7 @@ void updateLibrary_ShouldReturnUpdatedLibrary() throws Exception { } @Test - @TestWithSecurityContext + @WithMockUser void deleteLibrary_ShouldReturnSuccessMessage() throws Exception { // Given // (service method will be called) @@ -115,7 +115,7 @@ void deleteLibrary_ShouldReturnSuccessMessage() throws Exception { } @Test - @TestWithSecurityContext + @WithMockUser void updateModerationStatus_ShouldReturnUpdatedLibrary() throws Exception { // Given UpdateLibraryModerationRequest request = new UpdateLibraryModerationRequest(); diff --git a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java index ee0150f..f60706b 100644 --- a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java @@ -12,11 +12,9 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; -import org.springframework.security.test.context.TestWithSecurityContext; import org.springframework.test.web.servlet.MockMvc; import java.util.List; -import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -26,7 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** - * Integration тесты для AuthController и LibraryController + * Integration тесты для AuthController */ @WebMvcTest(AuthController.class) class AuthControllerTest { @@ -42,7 +40,6 @@ class AuthControllerTest { @Test void register_ShouldReturnAuthResponse() throws Exception { - // Given RegisterRequest request = new RegisterRequest(); request.setUsername("newuser"); request.setEmail("new@example.com"); @@ -50,49 +47,42 @@ void register_ShouldReturnAuthResponse() throws Exception { AuthResponse response = AuthResponse.builder() .token("jwt-token-here") - .username("newuser") - .role("USER") .build(); when(authService.register(any(RegisterRequest.class))).thenReturn(response); - // When & Then mockMvc.perform(post("/api/v1/auth/register") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.token").value("jwt-token-here")) - .andExpect(jsonPath("$.username").value("newuser")) - .andExpect(jsonPath("$.role").value("USER")); + .andExpect(jsonPath("$.token").value("jwt-token-here")); } @Test void login_ShouldReturnAuthResponse() throws Exception { - // Given AuthenticationRequest request = new AuthenticationRequest(); request.setUsername("testuser"); request.setPassword("password123"); AuthResponse response = AuthResponse.builder() .token("jwt-token-here") - .username("testuser") - .role("USER") .build(); when(authService.authenticate(any(AuthenticationRequest.class))).thenReturn(response); - // When & Then mockMvc.perform(post("/api/v1/auth/login") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.token").value("jwt-token-here")) - .andExpect(jsonPath("$.username").value("testuser")); + .andExpect(jsonPath("$.token").value("jwt-token-here")); } } +/** + * Integration тесты для LibraryController + */ @WebMvcTest(LibraryController.class) class LibraryControllerTest { @@ -107,13 +97,11 @@ class LibraryControllerTest { @Test void getAllLibraries_ShouldReturnPagedResponse() throws Exception { - // Given LibraryDto library = createTestLibrary(); Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); when(libraryService.getAllLibraries(any())).thenReturn(page); - // When & Then mockMvc.perform(get("/api/v1/libraries") .param("page", "0") .param("size", "10")) @@ -126,11 +114,9 @@ void getAllLibraries_ShouldReturnPagedResponse() throws Exception { @Test void getLibraryById_ShouldReturnLibrary() throws Exception { - // Given LibraryDto library = createTestLibrary(); when(libraryService.getLibraryById(1L)).thenReturn(library); - // When & Then mockMvc.perform(get("/api/v1/libraries/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) @@ -140,13 +126,11 @@ void getLibraryById_ShouldReturnLibrary() throws Exception { @Test void searchLibraries_ByQuery_ShouldReturnResults() throws Exception { - // Given LibraryDto library = createTestLibrary(); Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); when(libraryService.searchLibraries(eq("requests"), any())).thenReturn(page); - // When & Then mockMvc.perform(get("/api/v1/libraries/search") .param("query", "requests")) .andExpect(status().isOk()) @@ -156,13 +140,11 @@ void searchLibraries_ByQuery_ShouldReturnResults() throws Exception { @Test void searchLibraries_BySource_ShouldReturnFilteredResults() throws Exception { - // Given LibraryDto library = createTestLibrary(); Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); when(libraryService.getLibrariesBySource(eq("pypi"), any())).thenReturn(page); - // When & Then mockMvc.perform(get("/api/v1/libraries/search") .param("source", "pypi")) .andExpect(status().isOk()) @@ -171,13 +153,11 @@ void searchLibraries_BySource_ShouldReturnFilteredResults() throws Exception { @Test void getHealthyLibraries_ShouldReturnFilteredByMinScore() throws Exception { - // Given LibraryDto library = createTestLibrary(); library.setHealthScore(85); when(libraryService.getHealthyLibraries(80)).thenReturn(List.of(library)); - // When & Then mockMvc.perform(get("/api/v1/libraries/healthy") .param("minScore", "80")) .andExpect(status().isOk()) @@ -187,7 +167,6 @@ void getHealthyLibraries_ShouldReturnFilteredByMinScore() throws Exception { @Test void getLibraryHealth_ShouldReturnHealthMetrics() throws Exception { - // Given LibraryDto library = createTestLibrary(); library.setHealthScore(85); library.setLastRelease("2024-01-15T10:00:00"); @@ -197,7 +176,6 @@ void getLibraryHealth_ShouldReturnHealthMetrics() throws Exception { when(libraryService.getLibraryById(1L)).thenReturn(library); - // When & Then mockMvc.perform(get("/api/v1/libraries/1/health")) .andExpect(status().isOk()) .andExpect(jsonPath("$.overallScore").isNumber()) diff --git a/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java b/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java index 1df4470..c84dbea 100644 --- a/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java +++ b/backend/src/test/java/com/stackscout/service/impl/SubscriptionServiceImplTest.java @@ -183,7 +183,7 @@ void getSubscriptionStatus_WhenSubscribed_ShouldReturnCorrectStatus() { SubscriptionStatusDto result = subscriptionService.getSubscriptionStatus(1L, 1L); // Then - assertTrue(result.isSubscribed()); + assertTrue(result.getIsSubscribed()); assertEquals(5L, result.getSubscribersCount()); assertTrue(result.getNotificationsEnabled()); } @@ -195,7 +195,7 @@ void getSubscriptionStatus_WhenNotSubscribed_ShouldReturnFalse() { SubscriptionStatusDto result = subscriptionService.getSubscriptionStatus(1L, 1L); - assertFalse(result.isSubscribed()); + assertFalse(result.getIsSubscribed()); assertEquals(3L, result.getSubscribersCount()); } diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 847b556..6810148 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -7,7 +7,7 @@ const createJestConfig = nextJest({ const config: Config = { testEnvironment: 'jest-environment-jsdom', - setupFilesAfterEach: ['/jest.setup.ts'], + setupFilesAfterEnv: ['/jest.setup.ts'], moduleNameMapper: { '^@/(.*)$': '/src/$1', }, From 5b02713ca0e110edbfbe5a9065f68a8660be1615 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:13:33 +0500 Subject: [PATCH 3/9] refactor: replace @MockBean with @MockitoBean in AdminLibraryControllerTest and AuthControllerTest --- .../stackscout/controller/AdminLibraryControllerTest.java | 4 ++-- .../java/com/stackscout/controller/AuthControllerTest.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java index 1f17777..03295a2 100644 --- a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.bean.MockBean; +import org.springframework.boot.test.mock.mockito.MockitoBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -33,7 +33,7 @@ class AdminLibraryControllerTest { @Autowired private ObjectMapper objectMapper; - @MockBean + @MockitoBean private LibraryService libraryService; @Test diff --git a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java index f60706b..a0aeaaa 100644 --- a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.bean.MockBean; +import org.springframework.boot.test.mock.mockito.MockitoBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -35,7 +35,7 @@ class AuthControllerTest { @Autowired private ObjectMapper objectMapper; - @MockBean + @MockitoBean private AuthService authService; @Test @@ -92,7 +92,7 @@ class LibraryControllerTest { @Autowired private ObjectMapper objectMapper; - @MockBean + @MockitoBean private LibraryService libraryService; @Test From e1f69bca10d12886eb8e367904a1bcb0d2ad9939 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:18:41 +0500 Subject: [PATCH 4/9] refactor: move MockitoBean import to the correct package in AdminLibraryControllerTest and AuthControllerTest --- .../com/stackscout/controller/AdminLibraryControllerTest.java | 2 +- .../test/java/com/stackscout/controller/AuthControllerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java index 03295a2..3209a66 100644 --- a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java @@ -6,12 +6,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockitoBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import java.util.List; diff --git a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java index a0aeaaa..c795f1b 100644 --- a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java @@ -7,11 +7,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockitoBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import java.util.List; From e8e9f1631765d3f4fc7995fd2c6e80a6469d2b51 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:27:01 +0500 Subject: [PATCH 5/9] refactor: update test classes to use @MockitoBean for JwtAuthenticationFilter and adjust user roles in AdminLibraryControllerTest --- .../StackScoutApplicationTests.java | 2 + .../AdminLibraryControllerTest.java | 75 ++++++++----------- .../controller/AuthControllerTest.java | 26 +++++-- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java b/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java index c49070d..48fd397 100644 --- a/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java +++ b/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java @@ -3,11 +3,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; /** * Тесты для проверки корректности загрузки контекста приложения. */ @SpringBootTest +@ActiveProfiles("test") class StackScoutApplicationTests { @Test diff --git a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java index 3209a66..36634f4 100644 --- a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java @@ -1,6 +1,7 @@ package com.stackscout.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import com.stackscout.config.JwtAuthenticationFilter; import com.stackscout.dto.*; import com.stackscout.service.LibraryService; import org.junit.jupiter.api.Test; @@ -17,6 +18,7 @@ import java.util.List; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -36,8 +38,11 @@ class AdminLibraryControllerTest { @MockitoBean private LibraryService libraryService; + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + @Test - @WithMockUser + @WithMockUser(roles = "ADMIN") void getAllLibraries_ShouldReturnPagedResponse() throws Exception { // Given LibraryDto library = createTestLibrary(); @@ -46,87 +51,67 @@ void getAllLibraries_ShouldReturnPagedResponse() throws Exception { when(libraryService.getAllLibraries(any())).thenReturn(page); // When & Then - mockMvc.perform(get("/api/v1/admin/libraries") + mockMvc.perform(get("/api/admin/libraries") .param("page", "0") .param("size", "10")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.libraries").isArray()) - .andExpect(jsonPath("$.libraries[0].name").value("requests")) + .andExpect(jsonPath("$.content").isArray()) + .andExpect(jsonPath("$.content[0].name").value("requests")) .andExpect(jsonPath("$.totalElements").value(1)); } @Test - @WithMockUser + @WithMockUser(roles = "ADMIN") void createLibrary_ShouldReturnCreatedLibrary() throws Exception { // Given - CreateLibraryRequest request = new CreateLibraryRequest(); - request.setName("flask"); - request.setVersion("3.0.0"); - request.setSource("pypi"); - - LibraryDto created = new LibraryDto(); - created.setId(2L); - created.setName("flask"); - created.setVersion("3.0.0"); - created.setSource("pypi"); - - when(libraryService.createLibrary(any())).thenReturn(created); + LibraryDto library = createTestLibrary(); + when(libraryService.getLibraryById(1L)).thenReturn(library); // When & Then - mockMvc.perform(post("/api/v1/admin/libraries") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.library.name").value("flask")) - .andExpect(jsonPath("$.library.id").value(2)); + mockMvc.perform(get("/api/admin/libraries/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("requests")); } @Test - @WithMockUser + @WithMockUser(roles = "ADMIN") void updateLibrary_ShouldReturnUpdatedLibrary() throws Exception { // Given - UpdateLibraryRequest request = new UpdateLibraryRequest(); - request.setVersion("2.32.0"); - LibraryDto updated = createTestLibrary(); - updated.setVersion("2.32.0"); + updated.setHealthScore(92); - when(libraryService.updateLibrary(any(), any())).thenReturn(updated); + when(libraryService.recalculateHealthScore(1L)).thenReturn(updated); // When & Then - mockMvc.perform(put("/api/v1/admin/libraries/1") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) + mockMvc.perform(post("/api/admin/libraries/1/recalculate-health")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.library.version").value("2.32.0")); + .andExpect(jsonPath("$.healthScore").value(92)); } @Test - @WithMockUser + @WithMockUser(roles = "ADMIN") void deleteLibrary_ShouldReturnSuccessMessage() throws Exception { - // Given - // (service method will be called) - // When & Then - mockMvc.perform(delete("/api/v1/admin/libraries/1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.message").value("Библиотека успешно удалена")) - .andExpect(jsonPath("$.id").value("1")); + mockMvc.perform(delete("/api/admin/libraries/1")) + .andExpect(status().isNoContent()) + .andExpect(content().string("")); } @Test - @WithMockUser + @WithMockUser(roles = "ADMIN") void updateModerationStatus_ShouldReturnUpdatedLibrary() throws Exception { // Given UpdateLibraryModerationRequest request = new UpdateLibraryModerationRequest(); - // request.setModerationStatus("APPROVED"); + request.setModerationStatus(com.stackscout.model.ModerationStatus.VERIFIED); + request.setModerationNotes("Looks good"); LibraryDto updated = createTestLibrary(); - when(libraryService.updateModerationStatus(any(), any())).thenReturn(updated); + when(libraryService.updateModerationStatus(eq(1L), any())).thenReturn(updated); // When & Then - mockMvc.perform(patch("/api/v1/admin/libraries/1/moderation") + mockMvc.perform(patch("/api/admin/libraries/1/moderation") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) diff --git a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java index c795f1b..d4725a1 100644 --- a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java @@ -1,7 +1,9 @@ package com.stackscout.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import com.stackscout.config.JwtAuthenticationFilter; import com.stackscout.dto.*; +import com.stackscout.source.SourceRegistryService; import com.stackscout.service.AuthService; import com.stackscout.service.LibraryService; import org.junit.jupiter.api.Test; @@ -38,6 +40,9 @@ class AuthControllerTest { @MockitoBean private AuthService authService; + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + @Test void register_ShouldReturnAuthResponse() throws Exception { RegisterRequest request = new RegisterRequest(); @@ -89,12 +94,15 @@ class LibraryControllerTest { @Autowired private MockMvc mockMvc; - @Autowired - private ObjectMapper objectMapper; - @MockitoBean private LibraryService libraryService; + @MockitoBean + private SourceRegistryService sourceRegistryService; + + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + @Test void getAllLibraries_ShouldReturnPagedResponse() throws Exception { LibraryDto library = createTestLibrary(); @@ -143,6 +151,7 @@ void searchLibraries_BySource_ShouldReturnFilteredResults() throws Exception { LibraryDto library = createTestLibrary(); Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); + when(sourceRegistryService.normalize("pypi")).thenReturn("pypi"); when(libraryService.getLibrariesBySource(eq("pypi"), any())).thenReturn(page); mockMvc.perform(get("/api/v1/libraries/search") @@ -155,14 +164,15 @@ void searchLibraries_BySource_ShouldReturnFilteredResults() throws Exception { void getHealthyLibraries_ShouldReturnFilteredByMinScore() throws Exception { LibraryDto library = createTestLibrary(); library.setHealthScore(85); + Page page = new PageImpl<>(List.of(library), PageRequest.of(0, 10), 1); - when(libraryService.getHealthyLibraries(80)).thenReturn(List.of(library)); + when(libraryService.getAllLibraries(eq(80), any())).thenReturn(page); - mockMvc.perform(get("/api/v1/libraries/healthy") - .param("minScore", "80")) + mockMvc.perform(get("/api/v1/libraries/search") + .param("minHealthScore", "80")) .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].name").value("requests")) - .andExpect(jsonPath("$[0].healthScore").value(85)); + .andExpect(jsonPath("$.libraries[0].name").value("requests")) + .andExpect(jsonPath("$.libraries[0].healthScore").value(85)); } @Test From 09aa9f7834aaa126c86139feb6845a812e06f7bc Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:33:52 +0500 Subject: [PATCH 6/9] refactor: update application.yml to set default profile and enhance test configurations in AdminLibraryControllerTest and AuthControllerTest --- backend/src/main/resources/application.yml | 2 +- .../com/stackscout/StackScoutApplicationTests.java | 10 +++++++++- .../controller/AdminLibraryControllerTest.java | 2 ++ .../com/stackscout/controller/AuthControllerTest.java | 3 +++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index e639401..a32906d 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -7,7 +7,7 @@ spring: name: StackScout profiles: - active: dev + default: dev # Настройки подключения к базе данных PostgreSQL datasource: diff --git a/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java b/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java index 48fd397..17a2e63 100644 --- a/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java +++ b/backend/src/test/java/com/stackscout/StackScoutApplicationTests.java @@ -8,7 +8,15 @@ /** * Тесты для проверки корректности загрузки контекста приложения. */ -@SpringBootTest +@SpringBootTest(properties = { + "spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=PostgreSQL", + "spring.datasource.driver-class-name=org.h2.Driver", + "spring.datasource.username=sa", + "spring.datasource.password=", + "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect", + "spring.jpa.hibernate.ddl-auto=create-drop", + "spring.flyway.enabled=false" +}) @ActiveProfiles("test") class StackScoutApplicationTests { diff --git a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java index 36634f4..a099506 100644 --- a/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AdminLibraryControllerTest.java @@ -6,6 +6,7 @@ import com.stackscout.service.LibraryService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -27,6 +28,7 @@ * Integration тесты для AdminLibraryController */ @WebMvcTest(AdminLibraryController.class) +@AutoConfigureMockMvc(addFilters = false) class AdminLibraryControllerTest { @Autowired diff --git a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java index d4725a1..e351307 100644 --- a/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java +++ b/backend/src/test/java/com/stackscout/controller/AuthControllerTest.java @@ -8,6 +8,7 @@ import com.stackscout.service.LibraryService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -29,6 +30,7 @@ * Integration тесты для AuthController */ @WebMvcTest(AuthController.class) +@AutoConfigureMockMvc(addFilters = false) class AuthControllerTest { @Autowired @@ -89,6 +91,7 @@ void login_ShouldReturnAuthResponse() throws Exception { * Integration тесты для LibraryController */ @WebMvcTest(LibraryController.class) +@AutoConfigureMockMvc(addFilters = false) class LibraryControllerTest { @Autowired From bafd0405953bb1c220d5fa83d45d409a48843322 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:45:32 +0500 Subject: [PATCH 7/9] refactor: update deploy.yml to add workflow_dispatch trigger and refine conditional for deployment --- .github/workflows/deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1d8cf9b..ec3c764 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,6 +6,7 @@ on: types: - completed branches: [dev_s] + workflow_dispatch: env: REGISTRY: ghcr.io @@ -16,7 +17,7 @@ jobs: deploy: name: Build & Push Docker Images runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'dev_s') }} permissions: contents: read packages: write From 105c693adfba6147030beda500334c7ce9438efd Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 01:55:07 +0500 Subject: [PATCH 8/9] refactor: update CI and deploy workflows to remove branch restrictions and enhance deployment conditions --- .github/workflows/ci.yml | 2 -- .github/workflows/deploy.yml | 9 +++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dab77ab..79042f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,7 @@ name: CI Pipeline on: push: - branches: [dev_s, develop] pull_request: - branches: [dev_s, develop] env: JAVA_VERSION: '21' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ec3c764..2143404 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,7 +5,6 @@ on: workflows: ["CI Pipeline"] types: - completed - branches: [dev_s] workflow_dispatch: env: @@ -17,13 +16,15 @@ jobs: deploy: name: Build & Push Docker Images runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'dev_s') }} + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') }} permissions: contents: read packages: write steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }} - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -40,7 +41,7 @@ jobs: tags: | type=sha type=ref,event=branch - latest + type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Backend image uses: docker/build-push-action@v5 @@ -61,7 +62,7 @@ jobs: tags: | type=sha type=ref,event=branch - latest + type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Frontend image uses: docker/build-push-action@v5 From f6d4a590eef3a4cdb4f02d3b3761f0dbe5f5bf27 Mon Sep 17 00:00:00 2001 From: LINESKL Date: Thu, 16 Apr 2026 02:06:51 +0500 Subject: [PATCH 9/9] refactor: add Docker Buildx setup step in deploy workflow --- .github/workflows/deploy.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2143404..64a9723 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,6 +26,9 @@ jobs: with: ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: