diff --git a/.gitignore b/.gitignore
index f31da9b..c9e4ffb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -192,4 +192,5 @@ google-services.json
# Misc
# ---------------------------
*.orig.*
-app-example
\ No newline at end of file
+app-example
+newDeps/
\ No newline at end of file
diff --git a/__tests__/assignment/createAssignment.test.tsx b/__tests__/assignment/createAssignment.test.tsx
new file mode 100644
index 0000000..1ca299f
--- /dev/null
+++ b/__tests__/assignment/createAssignment.test.tsx
@@ -0,0 +1,82 @@
+import UpsertAssignment from "@/app/assignment/upsertAssignment";
+import { CheckSubjectCompletion } from "@/lib/progress";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+
+const mockSingle = jest.fn();
+const mockSelect = jest.fn(() => ({ single: mockSingle, }));
+const mockInsert = jest.fn(() => ({ select: mockSelect, }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ sId: "subject-123",
+ }),
+}));
+
+jest.mock("@/lib/progress", () => ({
+ CheckSubjectCompletion: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("@/lib/asyncStorage", () => ({
+ GetAssignmentNotificationId: jest.fn(() => Promise.resolve()),
+ SaveAssignmentNotificationId: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("expo-notifications", () => ({
+ scheduleNotificationAsync: jest.fn(() => Promise.resolve("notification-123")),
+ SchedulableTriggerInputTypes: {
+ DATE: "date",
+ },
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ },
+ from: jest.fn(() => ({
+ insert: mockInsert,
+ })),
+ },
+}));
+
+test("creates an assignment and navigates back", async () => {
+ mockSingle.mockResolvedValue({
+ data: {
+ aId: "assignment-123",
+ title: "create a simple test",
+ deadline: "",
+ },
+ error: null,
+ });
+
+ const screen = render();
+ fireEvent.changeText(screen.getByTestId("assignment-title-input"), "create a simple test");
+ fireEvent.press(screen.getByTestId("upsert-assignment-button"));
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("assignments");
+ expect(mockInsert).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: "create a simple test",
+ uId: "user-123",
+ sId: "subject-123",
+ })
+ );
+ expect(CheckSubjectCompletion).toHaveBeenCalledWith("subject-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/__tests__/assignment/deleteAssignment.test.tsx b/__tests__/assignment/deleteAssignment.test.tsx
new file mode 100644
index 0000000..e16e3bf
--- /dev/null
+++ b/__tests__/assignment/deleteAssignment.test.tsx
@@ -0,0 +1,141 @@
+import ViewDetailsAssignment from "@/app/assignment/viewDetailsAssignment";
+import { CheckSubjectCompletion } from "@/lib/progress";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+import { Alert } from "react-native";
+
+const mockAssignmentSingle = jest.fn();
+const mockAssignmentSelectEq = jest.fn(() => ({ single: mockAssignmentSingle, }));
+const mockAssignmentSelect = jest.fn(() => ({ eq: mockAssignmentSelectEq, }));
+const mockAssignmentDeleteEq = jest.fn();
+const mockAssignmentDelete = jest.fn(() => ({ eq: mockAssignmentDeleteEq, }));
+
+const mockTasksSelectEq = jest.fn();
+const mockTasksSelect = jest.fn(() => ({ eq: mockTasksSelectEq }));
+
+const mockSubjectSingle = jest.fn();
+const mockSubjectSelectEq = jest.fn(() => ({ single: mockSubjectSingle }));
+const mockSubjectSelect = jest.fn(() => ({ eq: mockSubjectSelectEq }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ aId: "assignment-123",
+ }),
+ useFocusEffect: (callback: () => void) => {
+ const React = require("react");
+ React.useEffect(callback, [callback]);
+ },
+}));
+
+jest.mock("@/lib/progress", () => ({
+ CheckSubjectCompletion: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ getSession: jest.fn(() =>
+ Promise.resolve({
+ data: {
+ session: {
+ user: { id: "user-123" },
+ },
+ },
+ })
+ ),
+ onAuthStateChange: jest.fn(() => ({
+ data: {
+ subscription: {
+ unsubscribe: jest.fn(),
+ },
+ },
+ })),
+ },
+ from: jest.fn((table: string) => {
+ if (table === "assignments") {
+ return {
+ select: mockAssignmentSelect,
+ delete: mockAssignmentDelete,
+ };
+ }
+
+ if (table === "tasks") {
+ return {
+ select: mockTasksSelect,
+ };
+ }
+
+ if (table === "subjects") {
+ return {
+ select: mockSubjectSelect,
+ };
+ }
+
+ return {};
+ }),
+ },
+}));
+
+const alertSpy = jest.spyOn(Alert, "alert");
+
+test("deletes a task and navigates back", async () => {
+ mockAssignmentSingle.mockResolvedValue({
+ data: {
+ aId: "assignment-123",
+ title: "create a simple test",
+ uId: "user-123",
+ sId: "subject-123"
+ },
+ error: null,
+ });
+ mockTasksSelectEq.mockResolvedValue({ data: [], error: null, })
+ mockSubjectSingle.mockResolvedValue({
+ data: {
+ sId: "subject-123",
+ title: "ikt205g26v",
+ color: "blue",
+ },
+ error: null,
+ });
+ mockAssignmentDeleteEq.mockResolvedValue({ error: null, });
+
+ const screen = render();
+
+ await screen.findByText("create a simple test");
+ await screen.findByText("ikt205g26v");
+
+ fireEvent.press(await screen.findByTestId("delete-assignment-button"));
+
+ expect(alertSpy).toHaveBeenCalledWith(
+ "Delete Assignment",
+ "Are you sure you want to delete this assignment?",
+ expect.any(Array),
+ );
+
+ const alertButtons = alertSpy.mock.calls[0][2];
+ const confirmDeleteButton = alertButtons[1];
+
+ await confirmDeleteButton.onPress();
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("assignments");
+ expect(mockAssignmentDelete).toHaveBeenCalled();
+ expect(mockAssignmentDeleteEq).toHaveBeenCalledWith("aId", "assignment-123");
+ expect(CheckSubjectCompletion).toHaveBeenCalledWith("subject-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
diff --git a/__tests__/assignment/editAssignment.test.tsx b/__tests__/assignment/editAssignment.test.tsx
new file mode 100644
index 0000000..4b647fe
--- /dev/null
+++ b/__tests__/assignment/editAssignment.test.tsx
@@ -0,0 +1,100 @@
+import UpsertAssignment from "@/app/assignment/upsertAssignment";
+import { CheckSubjectCompletion } from "@/lib/progress";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+
+const mockUpdateSingle = jest.fn();
+const mockUpdateSelect = jest.fn(() => ({ single: mockUpdateSingle, }));
+const mockUpdateEq = jest.fn(() => ({ select: mockUpdateSelect, }));
+const mockUpdate = jest.fn(() => ({ eq: mockUpdateEq, }));
+const mockSingle = jest.fn();
+const mockSelectEq = jest.fn(() => ({ single: mockSingle, }));
+const mockSelect = jest.fn(() => ({ eq: mockSelectEq, }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ aId: "assignment-123",
+ }),
+ useFocusEffect: (callback: () => void) => callback(),
+}));
+
+jest.mock("@/lib/progress", () => ({
+ CheckSubjectCompletion: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("@/lib/asyncStorage", () => ({
+ GetAssignmentNotificationId: jest.fn(() => Promise.resolve(null)),
+}));
+
+jest.mock("expo-notifications", () => ({
+ scheduleNotificationAsync: jest.fn(() => Promise.resolve("notification-123")),
+ SchedulableTriggerInputTypes: {
+ DATE: "date",
+ },
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ },
+ from: jest.fn(() => ({
+ select: mockSelect,
+ update: mockUpdate,
+ })),
+ },
+}));
+
+test("updates an assignment and navigates back", async () => {
+ mockSingle.mockResolvedValue({
+ data: {
+ aId: "assignment-123",
+ title: "create a simple test",
+ deadline: "2026-04-25",
+ uId: "user-123",
+ sId: "subject-123",
+ },
+ error: null,
+ });
+ mockUpdateSingle.mockResolvedValue({
+ data: {
+ aId: "assignment-123",
+ title: "create a harder test",
+ deadline: "2026-04-25",
+ uId: "user-123",
+ },
+ error: null,
+ });
+
+ const screen = render();
+ fireEvent.changeText(await screen.findByTestId("assignment-title-input"), "create a harder test");
+ fireEvent.press(screen.getByTestId("upsert-assignment-button"));
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("assignments");
+ expect(mockSelect).toHaveBeenCalled();
+ expect(mockUpdate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: "create a harder test",
+ uId: "user-123",
+ deadline: "2026-04-25",
+ })
+ );
+ expect(mockUpdateSingle).toHaveBeenCalled();
+ expect(CheckSubjectCompletion).toHaveBeenCalledWith("subject-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/__tests__/authGuard.test.tsx b/__tests__/authGuard.test.tsx
new file mode 100644
index 0000000..8796790
--- /dev/null
+++ b/__tests__/authGuard.test.tsx
@@ -0,0 +1,79 @@
+import TabLayout from "@/app/(tabs)/_layout";
+import { supabase } from "@/lib/supabase";
+import { render, waitFor } from "@testing-library/react-native";
+
+jest.mock("expo-router", () => {
+ const React = require("react");
+ const { Text, View } = require("react-native");
+
+ const MockTabs = ({ children }: { children?: React.ReactNode }) => (
+
+ tabs
+ {children}
+
+ );
+
+ MockTabs.Screen = () => null;
+
+ return {
+ Redirect: ({ href }: { href: string }) => redirect:{href},
+ Tabs: MockTabs,
+ router: {
+ push: jest.fn(),
+ },
+ };
+});
+
+jest.mock("expo-notifications", () => ({
+ getLastNotificationResponse: jest.fn(() => null),
+ addNotificationResponseReceivedListener: jest.fn(() => ({
+ remove: jest.fn(),
+ })),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getSession: jest.fn(),
+ onAuthStateChange: jest.fn(() => ({
+ data: {
+ subscription: {
+ unsubscribe: jest.fn(),
+ },
+ },
+ })),
+ },
+ },
+}));
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+test("redirects to login if there is no session", async () => {
+ (supabase.auth.getSession as jest.Mock).mockResolvedValue({
+ data: { session: null },
+ });
+
+ const screen = render();
+
+ await waitFor(() => {
+ expect(screen.getByText("redirect:/login")).toBeTruthy();
+ });
+});
+
+test("renders tabs when session exists", async () => {
+ (supabase.auth.getSession as jest.Mock).mockResolvedValue({
+ data: {
+ session: {
+ user: { id: "user-123" },
+ },
+ },
+ });
+
+ const screen = render();
+
+ await waitFor(() => {
+ expect(screen.getByText("tabs")).toBeTruthy();
+ });
+});
\ No newline at end of file
diff --git a/__tests__/subject/createSubject.test.tsx b/__tests__/subject/createSubject.test.tsx
new file mode 100644
index 0000000..fd42c2a
--- /dev/null
+++ b/__tests__/subject/createSubject.test.tsx
@@ -0,0 +1,52 @@
+import UpsertSubject from "@/app/subject/upsertSubject";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+
+const mockInsert = jest.fn();
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({}),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ },
+ from: jest.fn(() => ({
+ insert: mockInsert,
+ })),
+ },
+}));
+
+test("creates a subject and navigates back", async () => {
+ mockInsert.mockResolvedValue({ error: null });
+
+ const screen = render();
+ fireEvent.changeText(screen.getByTestId("subject-title-input"), "ikt205g26v");
+ fireEvent.press(screen.getByTestId("upsert-subject-button"));
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("subjects");
+ expect(mockInsert).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: "ikt205g26v",
+ uId: "user-123",
+ })
+ );
+ expect(router.back).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/__tests__/subject/deleteSubject.test.tsx b/__tests__/subject/deleteSubject.test.tsx
new file mode 100644
index 0000000..ceec2ff
--- /dev/null
+++ b/__tests__/subject/deleteSubject.test.tsx
@@ -0,0 +1,116 @@
+import ViewDetailsSubject from "@/app/subject/viewDetailsSubject";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+import { Alert } from "react-native";
+
+const mockSubjectSingle = jest.fn();
+const mockSubjectSelectEq = jest.fn(() => ({ single: mockSubjectSingle }));
+const mockSubjectSelect = jest.fn(() => ({ eq: mockSubjectSelectEq }));
+const mockSubjectDeleteEq = jest.fn();
+const mockSubjectDelete = jest.fn(() => ({ eq: mockSubjectDeleteEq }));
+
+const mockAssignmentsOrder = jest.fn();
+const mockAssignmentsEq = jest.fn(() => ({ order: mockAssignmentsOrder }));
+const mockAssignmentsSelect = jest.fn(() => ({ eq: mockAssignmentsEq }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ sId: "subject-123",
+ }),
+ useFocusEffect: (callback: () => void) => {
+ const React = require("react");
+ React.useEffect(callback, [callback]);
+ },
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ getSession: jest.fn(() =>
+ Promise.resolve({
+ data: {
+ session: {
+ user: { id: "user-123" },
+ },
+ },
+ })
+ ),
+ onAuthStateChange: jest.fn(() => ({
+ data: {
+ subscription: {
+ unsubscribe: jest.fn(),
+ },
+ },
+ })),
+ },
+ from: jest.fn((table) => {
+ if (table === "subjects") {
+ return {
+ select: mockSubjectSelect,
+ delete: mockSubjectDelete,
+ };
+ }
+
+ if (table === "assignments") {
+ return {
+ select: mockAssignmentsSelect,
+ };
+ }
+
+ return {};
+ }),
+ },
+}));
+
+const alertSpy = jest.spyOn(Alert, "alert");
+
+test("deletes a subject and navigates back", async () => {
+ mockSubjectSingle.mockResolvedValue({
+ data: {
+ sId: "subject-123",
+ title: "ikt205g26v",
+ uId: "user-123",
+ },
+ error: null,
+ });
+ mockAssignmentsOrder.mockResolvedValue({ data: [], error: null, })
+ mockSubjectDeleteEq.mockResolvedValue({ error: null, });
+
+ const screen = render();
+
+ await screen.findByText("ikt205g26v");
+
+ fireEvent.press(await screen.findByTestId("delete-subject-button"));
+
+ expect(alertSpy).toHaveBeenCalledWith(
+ "Delete Subject",
+ "Are you sure you want to delete this subject?",
+ expect.any(Array),
+ );
+
+ const alertButtons = alertSpy.mock.calls[0][2];
+ const confirmDeleteButton = alertButtons[1];
+
+ await confirmDeleteButton.onPress();
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("subjects");
+ expect(mockSubjectDelete).toHaveBeenCalled();
+ expect(mockSubjectDeleteEq).toHaveBeenCalledWith("sId", "subject-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
diff --git a/__tests__/subject/editSubject.test.tsx b/__tests__/subject/editSubject.test.tsx
new file mode 100644
index 0000000..cac538c
--- /dev/null
+++ b/__tests__/subject/editSubject.test.tsx
@@ -0,0 +1,70 @@
+import UpsertSubject from "@/app/subject/upsertSubject";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+
+const mockUpdateEq = jest.fn();
+const mockUpdate = jest.fn(() => ({ eq: mockUpdateEq, }));
+const mockSingle = jest.fn();
+const mockSelectEq = jest.fn(() => ({ single: mockSingle, }));
+const mockSelect = jest.fn(() => ({ eq: mockSelectEq, }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ sId: "subject-123",
+ }),
+ useFocusEffect: (callback: () => void) => callback(),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ },
+ from: jest.fn(() => ({
+ select: mockSelect,
+ update: mockUpdate,
+ })),
+ },
+}));
+
+test("updates a subject and navigates back", async () => {
+ mockSingle.mockResolvedValue({
+ data: {
+ sId: "subject-123",
+ title: "ikt205g26v",
+ uId: "user-123",
+ },
+ error: null,
+ });
+ mockUpdateEq.mockResolvedValue({ error: null, });
+
+ const screen = render();
+ fireEvent.changeText(await screen.findByTestId("subject-title-input"), "ikt206g26v");
+ fireEvent.press(screen.getByTestId("upsert-subject-button"));
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("subjects");
+ expect(mockSelect).toHaveBeenCalled();
+ expect(mockUpdate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: "ikt206g26v",
+ uId: "user-123",
+ })
+ );
+ expect(mockUpdateEq).toHaveBeenCalledWith("sId", "subject-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/__tests__/task/createTask.test.tsx b/__tests__/task/createTask.test.tsx
new file mode 100644
index 0000000..b83a008
--- /dev/null
+++ b/__tests__/task/createTask.test.tsx
@@ -0,0 +1,61 @@
+import UpsertTask from "@/app/task/upsertTask";
+import { CheckAssignmentCompletion } from "@/lib/progress";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+
+const mockInsert = jest.fn();
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ aId: "assignment-123",
+ }),
+}));
+
+jest.mock("@/lib/progress", () => ({
+ CheckAssignmentCompletion: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ },
+ from: jest.fn(() => ({
+ insert: mockInsert,
+ })),
+ },
+}));
+
+test("creates a task and navigates back", async () => {
+ mockInsert.mockResolvedValue({ error: null });
+
+ const screen = render();
+ fireEvent.changeText(screen.getByTestId("task-title-input"), "Read chapter 4");
+ fireEvent.press(screen.getByTestId("upsert-task-button"));
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("tasks");
+ expect(mockInsert).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: "Read chapter 4",
+ uId: "user-123",
+ aId: "assignment-123",
+ })
+ );
+ expect(CheckAssignmentCompletion).toHaveBeenCalledWith("assignment-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/__tests__/task/deleteTask.test.tsx b/__tests__/task/deleteTask.test.tsx
new file mode 100644
index 0000000..0ffd9ef
--- /dev/null
+++ b/__tests__/task/deleteTask.test.tsx
@@ -0,0 +1,150 @@
+import ViewDetailsTask from "@/app/task/viewDetailsTask";
+import { CheckAssignmentCompletion } from "@/lib/progress";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+import { Alert } from "react-native";
+
+const mockTaskSingle = jest.fn();
+const mockTaskSelectEq = jest.fn(() => ({ single: mockTaskSingle }));
+const mockTaskSelect = jest.fn(() => ({ eq: mockTaskSelectEq }));
+const mockTaskDeleteEq = jest.fn();
+const mockTaskDelete = jest.fn(() => ({ eq: mockTaskDeleteEq }));
+
+const mockAssignmentSingle = jest.fn();
+const mockAssignmentSelectEq = jest.fn(() => ({ single: mockAssignmentSingle }));
+const mockAssignmentSelect = jest.fn(() => ({ eq: mockAssignmentSelectEq }));
+
+const mockSubjectSingle = jest.fn();
+const mockSubjectSelectEq = jest.fn(() => ({ single: mockSubjectSingle }));
+const mockSubjectSelect = jest.fn(() => ({ eq: mockSubjectSelectEq }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ tId: "task-123",
+ }),
+ useFocusEffect: (callback: () => void) => {
+ const React = require("react");
+ React.useEffect(callback, [callback]);
+ },
+}));
+
+jest.mock("@/lib/progress", () => ({
+ CheckAssignmentCompletion: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ getSession: jest.fn(() =>
+ Promise.resolve({
+ data: {
+ session: {
+ user: { id: "user-123" },
+ },
+ },
+ })
+ ),
+ onAuthStateChange: jest.fn(() => ({
+ data: {
+ subscription: {
+ unsubscribe: jest.fn(),
+ },
+ },
+ })),
+ },
+ from: jest.fn((table: string) => {
+ if (table === "tasks") {
+ return {
+ select: mockTaskSelect,
+ delete: mockTaskDelete,
+ };
+ }
+
+ if (table === "assignments") {
+ return {
+ select: mockAssignmentSelect,
+ };
+ }
+
+ if (table === "subjects") {
+ return {
+ select: mockSubjectSelect,
+ };
+ }
+
+ return {};
+ }),
+ },
+}));
+
+const alertSpy = jest.spyOn(Alert, "alert");
+
+test("deletes a task and navigates back", async () => {
+ mockTaskSingle.mockResolvedValue({
+ data: {
+ tId: "task-123",
+ title: "Read chapter 4",
+ uId: "user-123",
+ aId: "assignment-123",
+ },
+ error: null,
+ });
+ mockAssignmentSingle.mockResolvedValue({
+ data: {
+ aId: "assignment-123",
+ title: "create a simple test",
+ uId: "user-123",
+ sId: "subject-123",
+ },
+ error: null,
+ });
+ mockSubjectSingle.mockResolvedValue({
+ data: {
+ sId: "subject-123",
+ title: "ikt205g26v",
+ color: "blue",
+ },
+ error: null,
+ });
+ mockTaskDeleteEq.mockResolvedValue({ error: null, });
+
+ const screen = render();
+
+ await screen.findByText("Read chapter 4");
+ await screen.findByText("ikt205g26v");
+
+ fireEvent.press(await screen.findByTestId("delete-task-button"));
+
+ expect(alertSpy).toHaveBeenCalledWith(
+ "Delete Task",
+ "Are you sure you want to delete this task?",
+ expect.any(Array),
+ );
+
+ const alertButtons = alertSpy.mock.calls[0][2];
+ const confirmDeleteButton = alertButtons[1];
+
+ await confirmDeleteButton.onPress();
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("tasks");
+ expect(mockTaskDelete).toHaveBeenCalled();
+ expect(mockTaskDeleteEq).toHaveBeenCalledWith("tId", "task-123");
+ expect(CheckAssignmentCompletion).toHaveBeenCalledWith("assignment-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
diff --git a/__tests__/task/editTask.test.tsx b/__tests__/task/editTask.test.tsx
new file mode 100644
index 0000000..24fe7cc
--- /dev/null
+++ b/__tests__/task/editTask.test.tsx
@@ -0,0 +1,78 @@
+import UpsertTask from "@/app/task/upsertTask";
+import { CheckAssignmentCompletion } from "@/lib/progress";
+import { supabase } from "@/lib/supabase";
+import { fireEvent, render, waitFor } from "@testing-library/react-native";
+import { router } from "expo-router";
+
+const mockUpdateEq = jest.fn();
+const mockUpdate = jest.fn(() => ({ eq: mockUpdateEq, }));
+const mockSingle = jest.fn();
+const mockSelectEq = jest.fn(() => ({ single: mockSingle, }));
+const mockSelect = jest.fn(() => ({ eq: mockSelectEq, }));
+
+jest.mock("expo-router", () => ({
+ router: {
+ back: jest.fn(),
+ replace: jest.fn(),
+ },
+ Stack: {
+ Screen: () => null,
+ },
+ useLocalSearchParams: () => ({
+ tId: "task-123",
+ }),
+ useFocusEffect: (callback: () => void) => callback(),
+}));
+
+jest.mock("@/lib/progress", () => ({
+ CheckAssignmentCompletion: jest.fn(() => Promise.resolve()),
+}));
+
+jest.mock("@/lib/supabase", () => ({
+ supabase: {
+ auth: {
+ getUser: jest.fn(() =>
+ Promise.resolve({
+ data: { user: { id: "user-123" } },
+ error: null,
+ })
+ ),
+ },
+ from: jest.fn(() => ({
+ select: mockSelect,
+ update: mockUpdate,
+ })),
+ },
+}));
+
+test("updates a task and navigates back", async () => {
+ mockSingle.mockResolvedValue({
+ data: {
+ tId: "task-123",
+ title: "Read chapter 4",
+ uId: "user-123",
+ aId: "assignment-123",
+ },
+ error: null,
+ });
+ mockUpdateEq.mockResolvedValue({ error: null, });
+
+ const screen = render();
+ fireEvent.changeText(await screen.findByTestId("task-title-input"), "Read chapter 5");
+ fireEvent.press(screen.getByTestId("upsert-task-button"));
+
+ await waitFor(() => {
+ expect(supabase.from).toHaveBeenCalledWith("tasks");
+ expect(mockSelect).toHaveBeenCalled();
+ expect(mockUpdate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: "Read chapter 5",
+ uId: "user-123",
+ aId: "assignment-123",
+ })
+ );
+ expect(mockUpdateEq).toHaveBeenCalledWith("tId", "task-123");
+ expect(CheckAssignmentCompletion).toHaveBeenCalledWith("assignment-123");
+ expect(router.back).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/app.json b/app.json
index 6dd84cd..deb766f 100644
--- a/app.json
+++ b/app.json
@@ -1,6 +1,6 @@
{
"expo": {
- "name": "Study-Sprint",
+ "name": "Study Sprint",
"slug": "Study-Sprint",
"owner": "ikt205g26v-g18",
"version": "1.0.0",
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index d9a261e..9f1e134 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -69,4 +69,4 @@ export default function TabLayout() {
);
-}
\ No newline at end of file
+}
diff --git a/app/(tabs)/subjects.tsx b/app/(tabs)/subjects.tsx
index 4998f62..0968e91 100644
--- a/app/(tabs)/subjects.tsx
+++ b/app/(tabs)/subjects.tsx
@@ -4,14 +4,9 @@ import { Subject } from '@/lib/types';
import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect } from 'expo-router';
import { useCallback, useEffect, useState } from 'react';
-import {
- ActivityIndicator,
- Alert,
- Pressable,
- ScrollView,
- Text,
- View,
-} from 'react-native';
+import { ActivityIndicator, Alert, Pressable, ScrollView, Text, View } from 'react-native';
+
+import type { SubjectColor } from '@/lib/subjectColors';
export default function Subjects() {
const [subjects, SetSubjects] = useState([]);
@@ -43,12 +38,16 @@ export default function Subjects() {
SetIsLoading(true);
+ SetIsLoading(true);
+
const { data, error } = await supabase
.from('subjects')
.select('*')
.eq('uId', session.user.id)
.order('lastChanged', { ascending: false });
+ SetIsLoading(false);
+
if (error) {
Alert.alert('Subjects could not be fetched, please try again');
SetIsLoading(false);
@@ -133,6 +132,13 @@ export default function Subjects() {
);
};
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
return (
diff --git a/app/(tabs)/timer.tsx b/app/(tabs)/timer.tsx
index eacca7d..12857f2 100644
--- a/app/(tabs)/timer.tsx
+++ b/app/(tabs)/timer.tsx
@@ -1,12 +1,16 @@
+import * as Haptics from 'expo-haptics';
import * as React from 'react';
import {
Animated,
Dimensions,
+ Easing,
StatusBar,
StyleSheet,
+ Text,
TouchableOpacity,
- View
+ View,
} from 'react-native';
+
const { width, height } = Dimensions.get('window');
const colors = {
@@ -15,147 +19,701 @@ const colors = {
text: '#ffffff',
};
-const timers = [...Array(13).keys()].map((i) => (i === 0 ? 1 : i * 5));
-const ITEM_SIZE = width * 0.38;
-const ITEM_SPACING = (width - ITEM_SIZE) / 2;
/*
-Har bare skrevet timeren som en egen tab til å begynne med.
-Planen er at når bruker starter en task så vil de få opp denne timeren
-som viser TaskName og Description der tallene står nå
-Kanskje en animert figur hvis vi får tid
-*/
-export default function App() {
- const scrollX = React.useRef(new Animated.Value(0)).current;
- const [duration, setDuration] = React.useState(timers[0])
- const timerAnimation = React.useRef(new Animated.Value(height)).current
- const buttonAnimation = React.useRef(new Animated.Value(0)).current
- const animation = React.useCallback(() => {
- Animated.sequence([
- Animated.timing(buttonAnimation, {
- toValue: 1,
- duration: 300,
- useNativeDriver: true
- }),
- Animated.timing(timerAnimation, {
- toValue: 0,
- duration: 300,
- useNativeDriver: true
- }),
- Animated.timing(timerAnimation, {
- toValue: height,
- duration: duration * 1000,
- useNativeDriver: true
- }),
- ]) .start(() => {
- Animated.timing(buttonAnimation, {
- toValue: 0,
- duration: 300,
- useNativeDriver: true
- }).start()
- })
- }, [duration])
+ TODO
+ Make timer count down even when app is un-focused or closed.
+ Set const endTime = Date.now() + duration and save that to the task, maybe?
+ Then trigger notif when endTime == Date.now()?
+ Then fetch endTime from DB -> if null then timer is inactive
+ if !null then set timer to endTime - Date.now() and start
+ Might have to save duration as well in DB to preserve timer animation persistance
+*/
+
+
+const TIMER_OPTIONS = [...Array(13).keys()].map((index) => (index === 0 ? 1 : index * 5));
+const ITEM_SIZE = width * 0.38;
+const ITEM_SPACING = (width - ITEM_SIZE) / 2;
+const TIMER_UNIT_IN_SECONDS = 60;
+const HOLD_TO_CANCEL_MS = 2000;
+const CANCEL_ANIMATION_DELAY_MS = 250;
+const BUTTON_PRESS_IN_MS = 80;
+const BUTTON_PRESS_OUT_MS = 140;
+
+const placeholderTask = {
+ name: 'Read chapter 4',
+ description: 'Focus on the summary questions and write down anything unclear.',
+};
+
+function formatTime(totalSeconds: number) {
+ const minutes = Math.floor(totalSeconds / 60);
+ const seconds = totalSeconds % 60;
+
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+}
+
+export default function TimerScreen() {
+ const [containerHeight, setContainerHeight] = React.useState(0);
+ const [duration, setDuration] = React.useState(TIMER_OPTIONS[0]);
+ const [timerIsRunning, setIsRunning] = React.useState(false);
+ const [timeRemaining, setTimeRemaining] = React.useState(0);
+
+ const scrollX = React.useRef(new Animated.Value(0)).current;
+ const timerAnimation = React.useRef(new Animated.Value(0)).current;
+ const buttonAnimation = React.useRef(new Animated.Value(0)).current;
+ const taskDetailsAnimation = React.useRef(new Animated.Value(0)).current;
+ const countdownAnimation = React.useRef(new Animated.Value(0)).current;
+ const cancelButtonAnimation = React.useRef(new Animated.Value(0)).current;
+ const pressedButtonAnimation = React.useRef(new Animated.Value(0)).current;
+ const focusModeAnimation = React.useRef(new Animated.Value(0)).current;
+ const cancelOverlayAnimation = React.useRef(new Animated.Value(0)).current;
+
+ const countdownRef = React.useRef | null>(null);
+ const cancelHoldTimeoutRef = React.useRef | null>(null);
+ const cancelHoldAnimationDelayRef = React.useRef | null>(null);
+ const runningAnimationRef = React.useRef(null);
+ const progressAnimationRef = React.useRef(null);
+ const sessionStartedAtRef = React.useRef(null);
+ const sessionDurationMsRef = React.useRef(0);
+ const cancelAccelStartedRef = React.useRef(false);
+ const cancelHoldActiveRef = React.useRef(false);
+ const cancelHoldIdRef = React.useRef(0);
+ const cancelHoldStartedAtRef = React.useRef(0);
+
+ React.useEffect(() => {
+ if (containerHeight > 0 && !timerIsRunning) {
+ timerAnimation.setValue(containerHeight);
+ }
+ }, [containerHeight, timerIsRunning, timerAnimation]);
+
+ const pressedButtonScale = pressedButtonAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [1, 0.9],
+ });
+
+ const cancelButtonTranslateY = cancelButtonAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [16, 0],
+ });
+
+ // Real timer progress comes from timerAnimation. The cancel hold adds a
+ // temporary visual offset on top so release/cancel logic does not fight the
+ // underlying progress animation.
+ const timerOverlayTranslateY = Animated.add(
+ timerAnimation,
+ cancelOverlayAnimation
+ ).interpolate({
+ inputRange: [0, Math.max(containerHeight, 1)],
+ outputRange: [0, Math.max(containerHeight, 1)],
+ extrapolate: 'clamp',
+ });
+
+ const countdownTranslateX = focusModeAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, -width * 0.3],
+ });
+
+ const countdownTranslateY = focusModeAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, -containerHeight * 0.35],
+ });
+
+ const countdownScale = focusModeAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [1, 0.55],
+ });
+
+ const startButtonOpacity = buttonAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [1, 0],
+ });
+
+ const startButtonTranslateY = buttonAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, 200],
+ });
+
+ const pickerOpacity = buttonAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [1, 0],
+ });
+
+ const taskDetailsOpacity = taskDetailsAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [0, 1],
+ });
+
+ const taskDetailsTranslateY = taskDetailsAnimation.interpolate({
+ inputRange: [0, 1],
+ outputRange: [20, 0],
+ });
+
+ const clearCountdownInterval = React.useCallback(() => {
+ if (countdownRef.current) {
+ clearInterval(countdownRef.current);
+ countdownRef.current = null;
+ }
+ }, []);
+
+ const clearCancelHoldTimeouts = React.useCallback(() => {
+ if (cancelHoldTimeoutRef.current) {
+ clearTimeout(cancelHoldTimeoutRef.current);
+ cancelHoldTimeoutRef.current = null;
+ }
+
+ if (cancelHoldAnimationDelayRef.current) {
+ clearTimeout(cancelHoldAnimationDelayRef.current);
+ cancelHoldAnimationDelayRef.current = null;
+ }
+ }, []);
+
+ const stopRunningAnimations = React.useCallback(() => {
+ runningAnimationRef.current?.stop();
+ runningAnimationRef.current = null;
+
+ progressAnimationRef.current?.stop();
+ progressAnimationRef.current = null;
+
+ cancelOverlayAnimation.stopAnimation();
+ }, [cancelOverlayAnimation]);
+
+ React.useEffect(() => {
+ return () => {
+ clearCountdownInterval();
+ clearCancelHoldTimeouts();
+ stopRunningAnimations();
+ };
+ }, [clearCancelHoldTimeouts, clearCountdownInterval, stopRunningAnimations]);
+
+ const animateButtonPress = React.useCallback(
+ (pressed: boolean) => {
+ Animated.timing(pressedButtonAnimation, {
+ toValue: pressed ? 1 : 0,
+ duration: pressed ? BUTTON_PRESS_IN_MS : BUTTON_PRESS_OUT_MS,
+ useNativeDriver: true,
+ }).start();
+ },
+ [pressedButtonAnimation]
+ );
+
+ const resetSessionValues = React.useCallback(() => {
+ sessionStartedAtRef.current = null;
+ sessionDurationMsRef.current = 0;
+ cancelHoldActiveRef.current = false;
+ cancelAccelStartedRef.current = false;
+
+ timerAnimation.setValue(containerHeight);
+ cancelOverlayAnimation.setValue(0);
+ setTimeRemaining(0);
+ setIsRunning(false);
+ }, [cancelOverlayAnimation, containerHeight, timerAnimation]);
+
+ const finishTimer = React.useCallback(() => {
+ clearCountdownInterval();
+
+ Animated.parallel([
+ Animated.timing(countdownAnimation, {
+ toValue: 0,
+ duration: 200,
+ useNativeDriver: true,
+ }),
+ Animated.timing(focusModeAnimation, {
+ toValue: 0,
+ duration: 250,
+ useNativeDriver: true,
+ }),
+ Animated.timing(taskDetailsAnimation, {
+ toValue: 0,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ ]).start(() => {
+ Animated.parallel([
+ Animated.timing(buttonAnimation, {
+ toValue: 0,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(cancelButtonAnimation, {
+ toValue: 0,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ ]).start(() => {
+ setIsRunning(false);
+ /* TODO
+ Implement store and send of ellapsed time value in seconds to DB
+ for total time spent statistic
+ */
+
+ resetSessionValues();
+ });
+ });
+ }, [
+ buttonAnimation,
+ cancelButtonAnimation,
+ clearCountdownInterval,
+ countdownAnimation,
+ focusModeAnimation,
+ resetSessionValues,
+ taskDetailsAnimation,
+ ]);
+
+ // This picks up the timer overlay animation from the current Y position and
+ // runs it to the bottom over the remaining session time.
+ const startProgressAnimation = React.useCallback(
+ (fromY: number) => {
+ const elapsedRatio = fromY / containerHeight;
+ const remainingMs = sessionDurationMsRef.current * (1 - elapsedRatio);
+
+ sessionStartedAtRef.current = Date.now() - sessionDurationMsRef.current * elapsedRatio;
+ timerAnimation.setValue(fromY);
+
+ const progressAnimation = Animated.timing(timerAnimation, {
+ toValue: containerHeight,
+ duration: remainingMs,
+ useNativeDriver: true,
+ });
+
+ progressAnimationRef.current = progressAnimation;
+ progressAnimation.start(({ finished }) => {
+ progressAnimationRef.current = null;
+
+ if (!finished) {
+ return;
+ }
+
+ finishTimer();
+ });
+ },
+ [containerHeight, finishTimer, timerAnimation]
+ );
+
+ const runStartSequence = React.useCallback(() => {
+ const runningAnimation = Animated.sequence([
+ Animated.parallel([
+ Animated.timing(buttonAnimation, {
+ toValue: 1,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(cancelButtonAnimation, {
+ toValue: 1,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(countdownAnimation, {
+ toValue: 1,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(timerAnimation, {
+ toValue: 0,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ ]),
+ Animated.timing(focusModeAnimation, {
+ toValue: 1,
+ duration: 450,
+ useNativeDriver: true,
+ }),
+ Animated.timing(taskDetailsAnimation, {
+ toValue: 1,
+ duration: 500,
+ useNativeDriver: true,
+ }),
+ ]);
+
+ runningAnimationRef.current = runningAnimation;
+ runningAnimation.start(({ finished }) => {
+ runningAnimationRef.current = null;
+
+ if (!finished) {
+ return;
+ }
+
+ startProgressAnimation(0);
+ });
+ }, [
+ buttonAnimation,
+ cancelButtonAnimation,
+ countdownAnimation,
+ focusModeAnimation,
+ startProgressAnimation,
+ taskDetailsAnimation,
+ timerAnimation,
+ ]);
+
+ const startCountdown = React.useCallback(
+ (totalSeconds: number) => {
+ setTimeRemaining(totalSeconds);
+ clearCountdownInterval();
+
+ countdownRef.current = setInterval(() => {
+ setTimeRemaining((currentTime) => {
+ if (currentTime <= 1) {
+ clearCountdownInterval();
+ return 0;
+ }
+
+ return currentTime - 1;
+ });
+ }, 1000);
+ },
+ [clearCountdownInterval]
+ );
+
+ const startTimerSession = React.useCallback(() => {
+ if (timerIsRunning || containerHeight === 0) {
+ return;
+ }
+
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
+ setIsRunning(true);
+
+ taskDetailsAnimation.setValue(0);
+ countdownAnimation.setValue(0);
+ cancelOverlayAnimation.setValue(0);
+
+ const totalSeconds = duration * TIMER_UNIT_IN_SECONDS;
+ sessionStartedAtRef.current = Date.now();
+ sessionDurationMsRef.current = totalSeconds * 1000;
+
+ startCountdown(totalSeconds);
+ runStartSequence();
+ }, [
+ cancelOverlayAnimation,
+ containerHeight,
+ countdownAnimation,
+ duration,
+ runStartSequence,
+ startCountdown,
+ taskDetailsAnimation,
+ timerIsRunning,
+ ]);
+
+ const cancelTimer = React.useCallback(() => {
+ if (!timerIsRunning) {
+ return;
+ }
+
+ clearCountdownInterval();
+ clearCancelHoldTimeouts();
+ stopRunningAnimations();
+
+ Animated.parallel([
+ Animated.timing(cancelButtonAnimation, {
+ toValue: 0,
+ duration: 180,
+ useNativeDriver: true,
+ }),
+ Animated.timing(taskDetailsAnimation, {
+ toValue: 0,
+ duration: 220,
+ useNativeDriver: true,
+ }),
+ Animated.timing(focusModeAnimation, {
+ toValue: 0,
+ duration: 250,
+ useNativeDriver: true,
+ }),
+ Animated.timing(countdownAnimation, {
+ toValue: 0,
+ duration: 180,
+ useNativeDriver: true,
+ }),
+ Animated.timing(timerAnimation, {
+ toValue: containerHeight,
+ duration: 300,
+ useNativeDriver: true,
+ }),
+ Animated.timing(cancelOverlayAnimation, {
+ toValue: 0,
+ duration: 120,
+ useNativeDriver: true,
+ }),
+ ]).start(() => {
+ Animated.timing(buttonAnimation, {
+ toValue: 0,
+ duration: 220,
+ useNativeDriver: true,
+ }).start(() => {
+ resetSessionValues();
+ });
+ });
+ }, [
+ buttonAnimation,
+ cancelButtonAnimation,
+ cancelOverlayAnimation,
+ clearCancelHoldTimeouts,
+ clearCountdownInterval,
+ containerHeight,
+ countdownAnimation,
+ focusModeAnimation,
+ resetSessionValues,
+ stopRunningAnimations,
+ taskDetailsAnimation,
+ timerAnimation,
+ timerIsRunning,
+ ]);
+
+ const handleCancelHoldStart = React.useCallback(() => {
+ animateButtonPress(true);
+ cancelHoldIdRef.current += 1;
+
+ const cancelHoldId = cancelHoldIdRef.current;
+ cancelHoldActiveRef.current = true;
+ cancelHoldStartedAtRef.current = Date.now();
+ cancelAccelStartedRef.current = false;
+
+ cancelHoldAnimationDelayRef.current = setTimeout(() => {
+ cancelHoldAnimationDelayRef.current = null;
+
+ if (!cancelHoldActiveRef.current || cancelHoldIdRef.current !== cancelHoldId) {
+ return;
+ }
+
+ // The hold starts with normal button feedback. After a short delay, we
+ // begin the accelerated red overlay preview so quick taps do not cause a
+ // jolt, while long holds still clearly show that cancel is about to fire.
+ cancelAccelStartedRef.current = true;
+ cancelOverlayAnimation.setValue(0);
+
+ const elapsedHoldMs = Date.now() - cancelHoldStartedAtRef.current;
+ const remainingHoldMs = Math.max(1, HOLD_TO_CANCEL_MS - elapsedHoldMs);
+ const sessionStartedAt = sessionStartedAtRef.current ?? Date.now();
+ const elapsedAtCancelMs = Date.now() + remainingHoldMs - sessionStartedAt;
+ const expectedProgress = elapsedAtCancelMs / sessionDurationMsRef.current;
+ const clampedProgress = Math.max(0, Math.min(expectedProgress, 1));
+ const expectedYAtCancel = containerHeight * clampedProgress;
+ const cancelOffset = Math.max(0, containerHeight - expectedYAtCancel);
+
+ Animated.timing(cancelOverlayAnimation, {
+ toValue: cancelOffset,
+ duration: remainingHoldMs,
+ easing: Easing.in(Easing.quad),
+ useNativeDriver: true,
+ }).start();
+ }, CANCEL_ANIMATION_DELAY_MS);
+
+ cancelHoldTimeoutRef.current = setTimeout(() => {
+ cancelHoldActiveRef.current = false;
+ cancelHoldIdRef.current += 1;
+ cancelAccelStartedRef.current = false;
+
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
+ cancelTimer();
+ cancelHoldTimeoutRef.current = null;
+ }, HOLD_TO_CANCEL_MS);
+ }, [animateButtonPress, cancelOverlayAnimation, cancelTimer, containerHeight]);
+
+ const handleCancelHoldEnd = React.useCallback(() => {
+ animateButtonPress(false);
+ cancelHoldActiveRef.current = false;
+ cancelHoldIdRef.current += 1;
+
+ clearCancelHoldTimeouts();
+
+ if (!cancelAccelStartedRef.current) {
+ return;
+ }
+
+ cancelAccelStartedRef.current = false;
+ cancelOverlayAnimation.stopAnimation((currentOffset) => {
+ cancelOverlayAnimation.setValue(currentOffset);
+ Animated.timing(cancelOverlayAnimation, {
+ toValue: 0,
+ duration: 750,
+ easing: Easing.in(Easing.bounce),
+ useNativeDriver: true,
+ }).start();
+ });
+ }, [animateButtonPress, cancelOverlayAnimation, clearCancelHoldTimeouts]);
+
+ const handleTimerPickerMomentumEnd = React.useCallback(
+ (event: { nativeEvent: { contentOffset: { x: number } } }) => {
+ if (timerIsRunning) {
+ return;
+ }
+
+ const index = Math.round(event.nativeEvent.contentOffset.x / ITEM_SIZE);
+ const clampedIndex = Math.max(0, Math.min(index, TIMER_OPTIONS.length - 1));
+ setDuration(TIMER_OPTIONS[clampedIndex]);
+ },
+ [timerIsRunning]
+ );
+
+ const renderTimerItem = React.useCallback(
+ ({ item, index }: { item: number; index: number }) => {
+ const inputRange = [
+ (index - 1) * ITEM_SIZE,
+ index * ITEM_SIZE,
+ (index + 1) * ITEM_SIZE,
+ ];
+
+ const baseOpacity = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.4, 1, 0.4],
+ });
+
+ const opacity = Animated.multiply(baseOpacity, pickerOpacity);
+ const scale = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.7, 1, 0.7],
+ });
+
+ return (
+
+
+ {item}
+
+
+ );
+ },
+ [pickerOpacity, scrollX]
+ );
- const opacity = buttonAnimation.interpolate({
- inputRange: [0, 1],
- outputRange: [1, 0]
- })
- const translateY = buttonAnimation.interpolate({
- inputRange: [0, 1],
- outputRange: [0, 200]
- })
-
return (
-
+ {
+ setContainerHeight(event.nativeEvent.layout.height);
+ }}
+ >
-
+
+ ]}
+ />
+
+
-
+ disabled={timerIsRunning}
+ onPress={startTimerSession}
+ onPressIn={() => animateButtonPress(true)}
+ onPressOut={() => animateButtonPress(false)}
+ >
+
+ Start
+ Sprint
+
+
+
+
+
+ Hold to end sprint
+
+
+
+
+
+ {formatTime(timeRemaining)}
+
+
- item.toString()}
+ style={[
+ styles.timerPickerWrapper,
+ {
+ top: containerHeight / 3,
+ },
+ ]}
+ >
+ item.toString()}
horizontal
bounces={false}
- onScroll={Animated.event(
- [{nativeEvent: {contentOffset: {x: scrollX}}}],
- { useNativeDriver: true}
- )}
+ onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }], {
+ useNativeDriver: true,
+ })}
showsHorizontalScrollIndicator={false}
- onMomentumScrollEnd={ev => {
- const index = Math.round(ev.nativeEvent.contentOffset.x / ITEM_SIZE)
- setDuration(timers[index]);
- }}
+ onMomentumScrollEnd={handleTimerPickerMomentumEnd}
snapToInterval={ITEM_SIZE}
- decelerationRate={"fast"}
- style={{flexGrow: 0}}
- contentContainerStyle={{
- paddingHorizontal: ITEM_SPACING
- }}
- renderItem={({item, index}) => {
- const inputRange = [
- (index - 1) * ITEM_SIZE,
- index * ITEM_SIZE,
- (index + 1) * ITEM_SIZE,
- ]
+ decelerationRate="fast"
+ style={styles.timerPickerList}
+ contentContainerStyle={styles.timerPickerContent}
+ renderItem={renderTimerItem}
+ />
+
- const opacity = scrollX.interpolate({
- inputRange,
- outputRange: [.4, 1, .4]
- })
- const scale = scrollX.interpolate({
- inputRange,
- outputRange: [.7, 1, .7]
- })
- return
-
- {item}
-
-
- }
- }
- />
-
+
+ {placeholderTask.name}
+ {placeholderTask.description}
+
);
}
@@ -165,16 +723,98 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: colors.black,
},
+ timerOverlay: {
+ backgroundColor: colors.red,
+ },
+ startButtonContainer: {
+ justifyContent: 'flex-end',
+ alignItems: 'center',
+ paddingBottom: 100,
+ },
roundButton: {
width: 80,
height: 80,
borderRadius: 80,
- backgroundColor: colors.red,
+ backgroundColor: '#beb9a7',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ timerPickerWrapper: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ flex: 1,
+ },
+ timerPickerList: {
+ flexGrow: 0,
+ },
+ timerPickerContent: {
+ paddingHorizontal: ITEM_SPACING,
+ },
+ timerOptionItem: {
+ width: ITEM_SIZE,
+ justifyContent: 'center',
+ alignItems: 'center',
},
text: {
fontSize: ITEM_SIZE * 0.8,
fontFamily: 'Menlo',
color: colors.text,
fontWeight: '900',
- }
-});
\ No newline at end of file
+ },
+ taskDetails: {
+ position: 'absolute',
+ top: height * 0.34,
+ left: 32,
+ right: 32,
+ alignItems: 'center',
+ },
+ taskName: {
+ color: colors.text,
+ fontSize: 32,
+ fontWeight: '800',
+ textAlign: 'center',
+ },
+ taskDescription: {
+ color: colors.text,
+ fontSize: 24,
+ lineHeight: 32,
+ marginTop: 20,
+ textAlign: 'center',
+ },
+ countdownText: {
+ fontSize: ITEM_SIZE * 0.32,
+ fontFamily: 'Menlo',
+ color: colors.text,
+ fontWeight: '900',
+ textAlign: 'center',
+ },
+ cancelButtonContainer: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ bottom: 44,
+ alignItems: 'center',
+ zIndex: 2,
+ },
+ cancelButton: {
+ minWidth: 112,
+ height: 44,
+ borderRadius: 22,
+ borderWidth: 1,
+ borderColor: 'rgba(155, 155, 155, 0.35)',
+ backgroundColor: '#beb9a7',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 22,
+ position: 'relative',
+ overflow: 'hidden',
+ },
+ countdownOverlay: {
+ position: 'absolute',
+ top: height / 3,
+ left: 0,
+ right: 0,
+ alignItems: 'center',
+ },
+});
diff --git a/app/assignment/upsertAssignment.tsx b/app/assignment/upsertAssignment.tsx
index d70a688..981c054 100644
--- a/app/assignment/upsertAssignment.tsx
+++ b/app/assignment/upsertAssignment.tsx
@@ -256,6 +256,7 @@ export default function UpsertAssignment() {
Title
>
);
-}
\ No newline at end of file
+}
diff --git a/app/assignment/viewDetailsAssignment.tsx b/app/assignment/viewDetailsAssignment.tsx
index ccb0def..b22834d 100644
--- a/app/assignment/viewDetailsAssignment.tsx
+++ b/app/assignment/viewDetailsAssignment.tsx
@@ -6,7 +6,7 @@ import type { Assignment, Task } from '@/lib/types';
import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useEffect, useState } from 'react';
-import { Alert, Pressable, SectionList, Text, View } from "react-native";
+import { ActivityIndicator, Alert, Pressable, SectionList, Text, View } from "react-native";
export default function ViewDetailsAssignment() {
@@ -14,6 +14,7 @@ export default function ViewDetailsAssignment() {
const [assignment, SetAssignment] = useState(null);
const [tasks, SetTasks] = useState([]);
const [session, SetSession] = useState(null);
+ const [isLoading, SetIsLoading] = useState(false);
const [subjectMeta, setSubjectMeta] = useState({
title: 'No Subject',
color: 'slate' as SubjectColor,
@@ -34,14 +35,17 @@ export default function ViewDetailsAssignment() {
[])
const GetAssignment = async (assignmentId: string) => {
- const { data, error } = await supabase
- .from('assignments')
- .select('*')
- .eq('aId', assignmentId)
- .single();
+ SetIsLoading(true);
+
+ const { data, error } = await supabase
+ .from('assignments')
+ .select('*')
+ .eq('aId', assignmentId)
+ .single();
+
+ SetIsLoading(false);
if (error || !data) {
- console.log('GetAssignment error:', error);
Alert.alert('Assignment could not be fetched, please try again');
return;
}
@@ -49,14 +53,17 @@ export default function ViewDetailsAssignment() {
SetAssignment(data);
if (data.sId) {
+ SetIsLoading(true);
+
const { data: subjectData, error: subjectError } = await supabase
.from('subjects')
.select('title, color')
.eq('sId', data.sId)
.single();
+ SetIsLoading(false);
+
if (subjectError || !subjectData) {
- console.log('GetSubjectMeta error:', subjectError);
setSubjectMeta({
title: 'Unknown Subject',
color: 'slate'
@@ -72,8 +79,12 @@ export default function ViewDetailsAssignment() {
};
const GetTasks = async (aId: string) => {
+ SetIsLoading(true);
+
const { data, error } = await supabase.from("tasks").select("*").eq("aId", aId);
+ SetIsLoading(false);
+
if (error) {
Alert.alert("Tasks could not be fetched, please try again");
return;
@@ -204,6 +215,14 @@ export default function ViewDetailsAssignment() {
? 0
: Math.round((completedTasks / totalTasks) * 100);
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
if (!assignment) {
return (
@@ -348,7 +367,7 @@ export default function ViewDetailsAssignment() {
className="mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3"
onPress={() =>
router.push({
- pathname: '/assignment/upsertAssignment',
+ pathname: '../assignment/upsertAssignment',
params: { aId: assignment.aId },
})
}
@@ -357,6 +376,7 @@ export default function ViewDetailsAssignment() {
DeleteAssignment(assignment.aId)}
>
@@ -371,7 +391,7 @@ export default function ViewDetailsAssignment() {
className="mb-6 mt-5 h-14 items-center justify-center rounded-2xl bg-accent"
onPress={() =>
router.push({
- pathname: '/task/createTask',
+ pathname: '../task/upsertTask',
params: { aId: assignment.aId },
})
}
@@ -453,7 +473,7 @@ export default function ViewDetailsAssignment() {
className="mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3"
onPress={() =>
router.push({
- pathname: '/task/editTask',
+ pathname: '../task/upsertTask',
params: { tId: item.tId },
})
}
diff --git a/app/subject/upsertSubject.tsx b/app/subject/upsertSubject.tsx
index b3bef3f..89d2e38 100644
--- a/app/subject/upsertSubject.tsx
+++ b/app/subject/upsertSubject.tsx
@@ -161,6 +161,7 @@ export default function UpsertSubject() {
Title
(null);
const [assignments, SetAssignments] = useState([]);
const [session, SetSession] = useState(null);
- const [isLoading, SetIsLoading] = useState(true);
+ const [isLoading, SetIsLoading] = useState(false);
const assignmentSections = [
{
@@ -49,12 +49,16 @@ export default function ViewDetailsSubject() {
}, []);
const GetSubject = async (subjectId: string) => {
+ SetIsLoading(true);
+
const { data, error } = await supabase
.from('subjects')
.select('*')
.eq('sId', subjectId)
.single();
+ SetIsLoading(false);
+
if (error) {
Alert.alert('Subject could not be fetched, please try again');
return;
@@ -64,12 +68,16 @@ export default function ViewDetailsSubject() {
};
const GetAssignments = async (subjectId: string) => {
+ SetIsLoading(true);
+
const { data, error } = await supabase
.from('assignments')
.select('*')
.eq('sId', subjectId)
.order('deadline', { ascending: true });
+ SetIsLoading(false);
+
if (error) {
Alert.alert('Assignments could not be fetched, please try again');
return;
@@ -119,20 +127,6 @@ export default function ViewDetailsSubject() {
}, [session, sId])
);
- useEffect(() => {
- const test = async () => {
- try {
- const { data, error } = await supabase.from('subjects').select('*').limit(1);
- console.log('test data:', data);
- console.log('test error:', error);
- } catch (err) {
- console.log('test crashed:', err);
- }
- };
-
- test();
- }, []);
-
const DeleteSubject = async (subjectId: string) => {
Alert.alert(
'Delete Subject',
@@ -391,7 +385,7 @@ export default function ViewDetailsSubject() {
className="mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3"
onPress={() =>
router.push({
- pathname: '/subject/upsertSubject',
+ pathname: '../subject/upsertSubject',
params: { sId: subject.sId },
})
}
@@ -402,6 +396,7 @@ export default function ViewDetailsSubject() {
DeleteSubject(subject.sId)}
>
@@ -416,7 +411,7 @@ export default function ViewDetailsSubject() {
className="mb-6 mt-5 h-14 items-center justify-center rounded-2xl bg-accent"
onPress={() =>
router.push({
- pathname: '/assignment/upsertAssignment',
+ pathname: '../assignment/upsertAssignment',
params: { sId: subject.sId },
})
}
@@ -502,7 +497,7 @@ export default function ViewDetailsSubject() {
className="mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3"
onPress={() =>
router.push({
- pathname: '/assignment/upsertAssignment',
+ pathname: '../assignment/upsertAssignment',
params: { aId: item.aId },
})
}
diff --git a/app/task/_layout.tsx b/app/task/_layout.tsx
index 1125408..06a3159 100644
--- a/app/task/_layout.tsx
+++ b/app/task/_layout.tsx
@@ -3,9 +3,7 @@ import { Stack } from "expo-router";
export default function TaskLayout() {
return (
-
-
-
+
);
diff --git a/app/task/editTask.tsx b/app/task/editTask.tsx
deleted file mode 100644
index 45f7f11..0000000
--- a/app/task/editTask.tsx
+++ /dev/null
@@ -1,255 +0,0 @@
-import { CheckAssignmentCompletion } from '@/lib/progress';
-import { supabase } from '@/lib/supabase';
-import type { Task } from '@/lib/types';
-import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
-import { useCallback, useState } from 'react';
-import {
- ActivityIndicator,
- Alert,
- Keyboard,
- KeyboardAvoidingView,
- Platform,
- Pressable,
- ScrollView,
- Text,
- TextInput,
- TouchableWithoutFeedback,
- View,
-} from 'react-native';
-
-export default function EditTask() {
- const { tId } = useLocalSearchParams<{ tId: string }>();
- const [task, SetTask] = useState(null);
- const [isSaving, SetIsSaving] = useState(false);
-
-
- const GetTask = async (tId: string) => {
- const { data, error } = await supabase.from("tasks").select("*").eq("tId", tId).single();
-
- if (error) {
- Alert.alert("Task could not be fetched, please try again");
- return;
- }
-
- SetTask(data ?? null);
- }
-
- useFocusEffect(
- useCallback(() => {
- if (tId) {
- GetTask(tId);
- }
- }, [tId])
- );
-
- const EditTask = async () => {
- if (!task) return;
-
- if(task.title.trim() === '') {
- Alert.alert("Title is required!");
- return;
- }
-
- const { data, error: userError } = await supabase.auth.getUser();
-
- if(userError || !data.user) {
- router.replace("../createUser");
- return;
- }
-
- SetIsSaving(true);
-
- const { error: dbError } = await supabase.from("tasks").update({
- title: task.title,
- description: task.description,
- isCompleted: task.isCompleted,
- lastChanged: new Date().toISOString(),
- uId: data.user.id,
- aId: task.aId,
- }).eq("tId", tId);
-
- SetIsSaving(false);
-
- if (dbError) {
- Alert.alert("Task could not be edited, please try again");
- return;
- }
-
- if (task.aId) {
- try {
- await CheckAssignmentCompletion(task.aId);
- } catch {
- Alert.alert("Failed to update assignment completion state");
- }
- }
-
- Alert.alert("Task successfully edited!");
- router.back();
- };
-
- const inputClassName =
- 'rounded-2xl border border-app-border bg-app-subtle px-4 py-3 text-base text-text-main';
-
- const labelClassName = 'mb-2 text-sm font-semibold text-text-secondary';
-
-
- return (
- <>
-
-
- {!task ? (
-
-
-
- Task not found
-
-
- The task could not be loaded.
-
-
- router.back()}
- >
-
- Go back
-
-
-
-
- ) : (
-
-
-
-
-
- Edit Task
-
-
- Update the task details and completion state.
-
-
-
-
-
- Title
-
- SetTask((prev) => (prev ? { ...prev, title: text } : prev))
- }
- returnKeyType="next"
- />
-
-
-
- Description
-
- SetTask((prev) =>
- prev ? { ...prev, description: text } : prev
- )
- }
- multiline
- textAlignVertical="top"
- />
-
-
-
- SetTask((prev) =>
- prev ? { ...prev, isCompleted: !prev.isCompleted } : prev
- )
- }
- disabled={isSaving}
- className={`mb-6 flex-row items-center rounded-2xl border p-4 ${
- task.isCompleted
- ? 'border-accent bg-accent-soft'
- : 'border-app-border bg-app-subtle'
- }`}
- >
-
- {task.isCompleted && (
-
- ✓
-
- )}
-
-
-
-
- Mark as completed
-
-
- You can change this again later.
-
-
-
-
-
- {isSaving ? (
-
-
-
- Saving...
-
-
- ) : (
-
- Save Changes
-
- )}
-
-
- router.back()}
- disabled={isSaving}
- >
-
- Cancel
-
-
-
-
-
-
- )}
- >
- );
-}
\ No newline at end of file
diff --git a/app/task/tasks.tsx b/app/task/tasks.tsx
deleted file mode 100644
index 7e08755..0000000
--- a/app/task/tasks.tsx
+++ /dev/null
@@ -1,277 +0,0 @@
-import { defaultStyles } from '@/constants/defaultStyles';
-import { CheckAssignmentCompletion } from '@/lib/progress';
-import { supabase } from '@/lib/supabase';
-import type { Task } from '@/lib/types';
-import { Ionicons } from '@expo/vector-icons';
-import { Session } from '@supabase/supabase-js';
-import { router, Stack, useFocusEffect } from 'expo-router';
-import { useCallback, useEffect, useState } from 'react';
-import {
- Alert,
- Pressable,
- SectionList,
- Text,
- View,
-} from 'react-native';
-
-export default function Tasks() {
- const [tasks, SetTasks] = useState([]);
- const [session, SetSession] = useState(null);
-
- const taskSections = [
- {
- title: 'Upcoming Tasks',
- data: tasks.filter((task) => !task.isCompleted),
- emptyMessage: 'No upcoming tasks',
- },
- {
- title: 'Completed Tasks',
- data: tasks.filter((task) => task.isCompleted),
- emptyMessage: 'No completed tasks',
- },
- ];
-
- useEffect(() => {
- supabase.auth
- .getSession()
- .then(({ data }) => SetSession(data.session ?? null));
-
- const { data: sub } = supabase.auth.onAuthStateChange(
- (_event, newSession) => {
- SetSession(newSession);
- }
- );
-
- return () => sub.subscription.unsubscribe();
- }, []);
-
- const GetTasks = async () => {
- const { data, error } = await supabase.from('tasks').select('*');
-
- if (error) {
- Alert.alert('Tasks could not be fetched, please try again');
- return;
- }
-
- SetTasks(data ?? []);
- };
-
- useFocusEffect(
- useCallback(() => {
- if (session) {
- GetTasks();
- }
- }, [session])
- );
-
- const DeleteTask = async (tId: string, aId: string) => {
- Alert.alert(
- 'Delete Task',
- 'Are you sure you want to delete this task?',
- [
- {
- text: 'Cancel',
- style: 'cancel',
- },
- {
- text: 'Delete',
- style: 'destructive',
- onPress: async () => {
- const { error } = await supabase
- .from('tasks')
- .delete()
- .eq('tId', tId);
-
- if (error) {
- Alert.alert('Task could not be deleted, please try again');
- return;
- }
-
- Alert.alert('Task deleted successfully!');
-
- try {
- await CheckAssignmentCompletion(aId);
- } catch {
- Alert.alert("Failed to update assignment completion state");
- }
-
- GetTasks();
- },
- },
- ]
- );
- };
-
- return (
-
- (
-
-
-
-
-
- await supabase.auth.signOut()}
- >
-
- Logout
-
-
-
- ),
- }}
- />
-
-
-
-
- Tasks
-
-
- Break assignments into small steps and keep your progress clear.
-
-
-
- router.push('/task/createTask')}
- >
-
- Create Task
-
-
-
- item.tId}
- showsVerticalScrollIndicator={false}
- stickySectionHeadersEnabled={false}
- contentContainerStyle={{
- paddingBottom: 32,
- }}
- renderSectionHeader={({ section: { title, data } }) => (
-
-
- {title}
-
-
-
-
- {data.length}
-
-
-
- )}
- renderItem={({ item }) => {
- const isOwner = session?.user.id === item.uId;
-
- return (
-
-
- router.push({
- pathname: '/task/viewDetailsTask',
- params: { tId: item.tId },
- })
- }
- >
-
-
- {item.isCompleted && (
-
- ✓
-
- )}
-
-
-
-
- {item.title}
-
-
- {item.description ? (
-
- {item.description}
-
- ) : null}
-
-
-
- {item.isCompleted ? 'Completed' : 'In progress'}
-
-
-
-
-
-
- {isOwner && (
-
-
- router.push({
- pathname: '/task/editTask',
- params: { tId: item.tId },
- })
- }
- >
-
- Edit
-
-
-
- DeleteTask(item.tId, item.aId)}
- >
-
- Delete
-
-
-
- )}
-
- );
- }}
- renderSectionFooter={({ section }) =>
- section.data.length === 0 ? (
-
-
- {section.emptyMessage}
-
-
- Tasks for this assignment will show up here.
-
-
- ) : (
-
- )
- }
- />
-
-
- );
-}
\ No newline at end of file
diff --git a/app/task/createTask.tsx b/app/task/upsertTask.tsx
similarity index 66%
rename from app/task/createTask.tsx
rename to app/task/upsertTask.tsx
index 3b70e84..d29292a 100644
--- a/app/task/createTask.tsx
+++ b/app/task/upsertTask.tsx
@@ -1,8 +1,9 @@
import { defaultStyles } from '@/constants/defaultStyles';
import { CheckAssignmentCompletion } from '@/lib/progress';
import { supabase } from '@/lib/supabase';
+import type { Task } from '@/lib/types';
import { router, Stack, useLocalSearchParams } from 'expo-router';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
@@ -17,15 +18,57 @@ import {
View,
} from 'react-native';
-export default function CreateTask() {
- const aId = (useLocalSearchParams().aId as string) ?? null;
+export default function UpsertTask() {
+ const { tId, aId: routeAId } = useLocalSearchParams<{
+ tId?: string;
+ aId?: string;
+ }>();
+
+ const isEditMode = Boolean(tId);
const [title, SetTitle] = useState('');
const [description, SetDescription] = useState('');
const [isCompleted, SetIsCompleted] = useState(false);
+ const [assignmentId, SetAssignmentId] = useState(routeAId ?? null);
+
+ const [isLoading, SetIsLoading] = useState(isEditMode);
const [isSaving, SetIsSaving] = useState(false);
- const CreateTask = async () => {
+ useEffect(() => {
+ if (!isEditMode || !tId) {
+ SetIsLoading(false);
+ return;
+ }
+
+ const loadTask = async () => {
+ SetIsLoading(true);
+
+ const { data, error } = await supabase
+ .from('tasks')
+ .select('*')
+ .eq('tId', tId)
+ .single();
+
+ SetIsLoading(false);
+
+ if (error || !data) {
+ Alert.alert('Task could not be loaded, please try again');
+ router.back();
+ return;
+ }
+
+ const task = data as Task;
+
+ SetTitle(task.title ?? '');
+ SetDescription(task.description ?? '');
+ SetIsCompleted(task.isCompleted ?? false);
+ SetAssignmentId(task.aId ?? routeAId ?? null);
+ };
+
+ loadTask();
+ }, [isEditMode, tId, routeAId]);
+
+ const handleSubmit = async () => {
if (title.trim() === '') {
Alert.alert('Title is required!');
return;
@@ -34,42 +77,55 @@ export default function CreateTask() {
const { data, error: userError } = await supabase.auth.getUser();
if (userError || !data.user) {
- router.replace('../createUser');
+ router.replace('/login');
+ return;
+ }
+
+ if (!assignmentId) {
+ Alert.alert('Missing assignment', 'This task is not linked to an assignment.');
return;
}
SetIsSaving(true);
- const { error: dbError } = await supabase.from('tasks').insert({
+ const payload = {
title: title.trim(),
description: description.trim(),
isCompleted,
lastChanged: new Date().toISOString(),
uId: data.user.id,
- aId,
- });
+ aId: assignmentId,
+ };
- if (dbError) {
+ const result =
+ isEditMode && tId
+ ? await supabase.from('tasks').update(payload).eq('tId', tId)
+ : await supabase.from('tasks').insert(payload);
+
+ if (result.error) {
SetIsSaving(false);
- Alert.alert('Task could not be created, please try again');
+ Alert.alert(
+ isEditMode
+ ? 'Task could not be updated, please try again'
+ : 'Task could not be created, please try again'
+ );
return;
}
- Alert.alert('Task successfully created!');
-
- if (aId) {
- try {
- await CheckAssignmentCompletion(aId);
- } catch {
- Alert.alert("Failed to update assignment completion state");
- }
+ try {
+ await CheckAssignmentCompletion(assignmentId);
+ } catch {
+ SetIsSaving(false);
+ Alert.alert('Failed to update assignment completion state');
+ return;
}
- SetTitle('');
- SetDescription('');
- SetIsCompleted(false);
SetIsSaving(false);
+ Alert.alert(
+ isEditMode ? 'Task successfully updated!' : 'Task successfully created!'
+ );
+
router.back();
};
@@ -78,11 +134,19 @@ export default function CreateTask() {
const labelClassName = 'mb-2 text-sm font-semibold text-text-secondary';
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
return (
<>
@@ -104,10 +168,12 @@ export default function CreateTask() {
>
- Create Task
+ {isEditMode ? 'Edit Task' : 'Create Task'}
- Add a small step to move this assignment forward.
+ {isEditMode
+ ? 'Update this task and keep your assignment moving forward.'
+ : 'Add a small step to move this assignment forward.'}
@@ -115,8 +181,10 @@ export default function CreateTask() {
Title
{isSaving ? (
- Creating...
+ {isEditMode ? 'Saving...' : 'Creating...'}
) : (
- Create Task
+ {isEditMode ? 'Save Changes' : 'Create Task'}
)}
diff --git a/app/task/viewDetailsTask.tsx b/app/task/viewDetailsTask.tsx
index e9da2d0..3791792 100644
--- a/app/task/viewDetailsTask.tsx
+++ b/app/task/viewDetailsTask.tsx
@@ -6,13 +6,14 @@ import type { Task } from '@/lib/types';
import { Session } from '@supabase/supabase-js';
import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
import { useCallback, useEffect, useState } from 'react';
-import { Alert, Pressable, Text, View } from 'react-native';
+import { ActivityIndicator, Alert, Pressable, Text, View } from 'react-native';
export default function ViewDetailsTask() {
const { tId } = useLocalSearchParams<{ tId: string }>();
const [task, SetTask] = useState(null);
const [session, SetSession] = useState(null);
+ const [isLoading, SetIsLoading] = useState(false);
const [contextMeta, setContextMeta] = useState({
subjectTitle: 'No Subject',
assignmentTitle: 'No Assignment',
@@ -30,14 +31,17 @@ export default function ViewDetailsTask() {
}, []);
const GetTask = async (taskId: string) => {
+ SetIsLoading(true);
+
const { data, error } = await supabase
.from('tasks')
.select('*')
.eq('tId', taskId)
.single();
+
+ SetIsLoading(false);
if (error || !data) {
- console.log('GetTask error:', error);
Alert.alert('Task could not be fetched, please try again');
return;
}
@@ -45,14 +49,17 @@ export default function ViewDetailsTask() {
SetTask(data);
if (data.aId) {
+ SetIsLoading(true);
+
const { data: assignmentData, error: assignmentError } = await supabase
.from('assignments')
.select('title, sId')
.eq('aId', data.aId)
.single();
+ SetIsLoading(false);
+
if (assignmentError || !assignmentData) {
- console.log('GetTaskAssignment error:', assignmentError);
setContextMeta({
subjectTitle: 'Unknown Subject',
assignmentTitle: 'Unknown Assignment',
@@ -62,14 +69,17 @@ export default function ViewDetailsTask() {
}
if (assignmentData.sId) {
+ SetIsLoading(true);
+
const { data: subjectData, error: subjectError } = await supabase
.from('subjects')
.select('title, color')
.eq('sId', assignmentData.sId)
.single();
+ SetIsLoading(false);
+
if (subjectError || !subjectData) {
- console.log('GetTaskSubject error:', subjectError);
setContextMeta({
subjectTitle: 'Unknown Subject',
assignmentTitle: assignmentData.title ?? 'Unknown Assignment',
@@ -138,6 +148,14 @@ export default function ViewDetailsTask() {
const colorSet = getSubjectColorSet(contextMeta.subjectColor);
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
if (!task) {
return (
@@ -263,7 +281,7 @@ export default function ViewDetailsTask() {
className="mr-3 flex-1 items-center justify-center rounded-2xl border border-app-border bg-app-subtle py-3"
onPress={() =>
router.push({
- pathname: '/task/editTask',
+ pathname: '../task/upsertTask',
params: { tId: task.tId },
})
}
@@ -274,6 +292,7 @@ export default function ViewDetailsTask() {
DeleteTask(task.tId)}
>
@@ -286,4 +305,4 @@ export default function ViewDetailsTask() {
);
-}
\ No newline at end of file
+}
diff --git a/assets/study-sprint-image-pack/android-icon-background.png b/assets/study-sprint-image-pack/android-icon-background.png
new file mode 100644
index 0000000..ce57697
Binary files /dev/null and b/assets/study-sprint-image-pack/android-icon-background.png differ
diff --git a/assets/study-sprint-image-pack/android-icon-foreground.png b/assets/study-sprint-image-pack/android-icon-foreground.png
new file mode 100644
index 0000000..d9eabcf
Binary files /dev/null and b/assets/study-sprint-image-pack/android-icon-foreground.png differ
diff --git a/assets/study-sprint-image-pack/android-icon-monochrome.png b/assets/study-sprint-image-pack/android-icon-monochrome.png
new file mode 100644
index 0000000..bbf65ed
Binary files /dev/null and b/assets/study-sprint-image-pack/android-icon-monochrome.png differ
diff --git a/assets/study-sprint-image-pack/favicon.png b/assets/study-sprint-image-pack/favicon.png
new file mode 100644
index 0000000..a5be21f
Binary files /dev/null and b/assets/study-sprint-image-pack/favicon.png differ
diff --git a/assets/study-sprint-image-pack/icon.png b/assets/study-sprint-image-pack/icon.png
new file mode 100644
index 0000000..d6d20a2
Binary files /dev/null and b/assets/study-sprint-image-pack/icon.png differ
diff --git a/assets/study-sprint-image-pack/master.png b/assets/study-sprint-image-pack/master.png
new file mode 100644
index 0000000..d6d20a2
Binary files /dev/null and b/assets/study-sprint-image-pack/master.png differ
diff --git a/assets/study-sprint-image-pack/splash-icon.png b/assets/study-sprint-image-pack/splash-icon.png
new file mode 100644
index 0000000..d6d20a2
Binary files /dev/null and b/assets/study-sprint-image-pack/splash-icon.png differ
diff --git a/lib/notifications.ts b/lib/notifications.ts
index 43716ed..c03cb9a 100644
--- a/lib/notifications.ts
+++ b/lib/notifications.ts
@@ -36,4 +36,4 @@ export async function RegisterForLocalNotificationsAsync() {
HandleRegistrationError('Permission not granted for local notifications');
return;
}
-}
\ No newline at end of file
+}
diff --git a/notes/work-report-2026-04-24-to-25.md b/notes/work-report-2026-04-24-to-25.md
new file mode 100644
index 0000000..4ac50bb
--- /dev/null
+++ b/notes/work-report-2026-04-24-to-25.md
@@ -0,0 +1,71 @@
+# CRUD Testing Summary (React Native + Jest + Supabase)
+
+## What these tests are about
+Tests verify **app behavior**, not Supabase itself.
+
+They check:
+- User interaction works
+- Correct database functions are called
+- Navigation happens after actions
+
+---
+
+## CRUD Breakdown
+
+### CREATE
+- User inputs data
+- `insert()` is called
+- App navigates back
+
+Flow:
+User → type → press create → insert() → router.back()
+
+---
+
+### READ
+- Data is fetched (`select().eq().single()`)
+- State updates
+- UI renders correct content
+
+---
+
+### UPDATE
+- Existing data is loaded
+- User edits input
+- `update().eq()` is called with correct values
+- Navigation happens
+
+---
+
+### DELETE
+- User presses delete
+- `Alert.alert()` is triggered
+- Confirm button (`onPress`) is manually called in test
+- `delete().eq()` runs
+- Navigation happens
+
+---
+
+## Why mocking is used
+- No real database calls
+- Faster tests
+- Full control over success/error cases
+- No side effects (no real data created/deleted)
+
+---
+
+## Mock rule
+The mock must match the real call chain:
+
+Real:
+from → update → eq → select → single
+
+Mock:
+from() → update() → eq() → select() → single()
+
+If not → errors like:
+".select is not a function"
+
+---
+
+https://oss.callstack.com/react-native-testing-library/
diff --git a/notes/work-report-timer-2026-04-21.md b/notes/work-report-timer-2026-04-21.md
new file mode 100644
index 0000000..c8a641c
--- /dev/null
+++ b/notes/work-report-timer-2026-04-21.md
@@ -0,0 +1,92 @@
+# Timer Element Work Report
+
+## #Overview
+This note documents the timer work completed by **Chris Sanden** in the Study-Sprint project.
+
+The git history shows a dedicated timer commit:
+- Commit: `d50301cb04837b196110cea43ff15c0493c5fac2`
+- Short hash: `d50301c`
+- Author: `Chris Sanden `
+- Date: `2026-04-21`
+- Message: `First draft of timer element`
+- File added: `app/(tabs)/timer.tsx`
+- Branch references at inspection time: `timer`, `origin/timer`
+
+---
+
+## #ImplementedFeatures
+
+### #TimerTab
+Created the first draft of a standalone timer screen:
+- Added `app/(tabs)/timer.tsx`
+- Implemented the timer as its own tab while the final task-start flow is still planned
+- Used React Native and Expo tab routing conventions already present in the project
+
+---
+
+### #DurationSelector
+Implemented a horizontal animated selector for timer durations:
+- Uses `Animated.FlatList`
+- Supports snap scrolling with `snapToInterval`
+- Shows selectable durations from `1` to `60`
+- Uses scaled and faded text animation so the centered duration is emphasized
+- Updates the selected duration when scrolling ends
+
+---
+
+### #TimerAnimation
+Implemented the first timer start animation:
+- Added a circular start button
+- Button fades and moves down after the timer starts
+- Timer overlay animates into view
+- Timer overlay then animates out based on the selected duration
+- Uses `Animated.sequence` and `useNativeDriver`
+
+---
+
+## #UserInterface
+
+The timer screen includes:
+- Full-screen dark background
+- Red timer overlay
+- Large centered duration numbers
+- Circular red start button near the bottom of the screen
+- Hidden status bar for a focused timer view
+
+The visual direction is a simple first draft intended to make the timer interaction testable before deeper integration with tasks.
+
+---
+
+## #PlannedIntegration
+
+The in-code note describes the intended next step:
+- Keep the timer as a separate tab initially
+- Later open the timer when a user starts a task
+- Replace the current duration-number area with task information such as:
+ - Task name
+ - Task description
+- Potentially add an animated character or visual element if time allows
+
+---
+
+## #GitEvidence
+
+The work attributed to Chris is supported by this git log entry:
+
+```text
+d50301c Chris Sanden 2026-04-21 First draft of timer element
+```
+
+The commit added one new file:
+
+```text
+A app/(tabs)/timer.tsx
+```
+
+The file was later also touched in commit `cb6368a` by `Teodor` on `2026-04-22` as part of broader UI and routing fixes. The original timer implementation documented here is the `d50301c` commit authored by Chris.
+
+---
+
+## #Conclusion
+
+Chris implemented the first functional timer draft for the application. The work established a standalone timer tab, duration selection, animated start behavior, and a clear path for later connecting the timer to task-start workflows.
diff --git a/notes/work-report-timer-2026-04-22.md b/notes/work-report-timer-2026-04-22.md
new file mode 100644
index 0000000..e83478f
--- /dev/null
+++ b/notes/work-report-timer-2026-04-22.md
@@ -0,0 +1,151 @@
+# Timer UI and Countdown Work Report
+
+## #Overview
+Today the standalone timer screen was developed further before wiring it into the task system.
+
+The main focus was improving the timer interaction and learning how the React Native animation flow works. The timer is still being treated as its own tab for now, with placeholder task data used in place of real task integration.
+
+---
+
+## #ImplementedFeatures
+
+### #TaskInformationPlaceholder
+Added placeholder task information to the timer screen:
+- Placeholder task name
+- Placeholder task description
+- Fade-in animation when the timer starts
+- Fade-out animation when the timer finishes
+
+This prepares the timer UI for the later task integration, where the placeholder values can be replaced by real task data.
+
+---
+
+### #AdjacentTimerFade
+Updated the timer duration selector so adjacent numbers fade away when the timer starts:
+- The centered selected value remains visible
+- Neighboring values fade out during the active timer state
+- Neighboring values are intended to fade back in after the timer finishes
+
+This was implemented by separating the normal picker opacity from the active timer opacity and combining them with `Animated.add` and `Animated.multiply`.
+
+---
+
+### #MeasuredTimerHeight
+Started adjusting the timer overlay to use the measured screen/container height:
+- Added `containerHeight`
+- Added `onLayout` to measure the actual timer screen area
+- Updated timer overlay movement to use the measured container height
+
+This was done because the full window height does not always match the visible tab screen area when headers, tab bars, or safe areas are involved.
+
+---
+
+### #CountdownDisplay
+Added countdown display logic:
+- Added `timeRemaining`
+- Added `selectedIndex`
+- Added `formatTime(totalSeconds)`
+- Converted the selected timer value into a `MM:SS` display while running
+- Added `TIMER_UNIT_IN_SECONDS` so timer values can behave as seconds during development and minutes later
+
+Current development behavior:
+- `TIMER_UNIT_IN_SECONDS = 1`
+- Selecting `5` means a 5-second timer
+
+Planned production behavior:
+- `TIMER_UNIT_IN_SECONDS = 60`
+- Selecting `5` means a 5-minute timer
+
+---
+
+### #CountdownFadeControl
+Started separating countdown visibility from the rest of the timer UI:
+- Added `countdownAnimation`
+- Added `showCountdownText`
+- Began separating the `MM:SS` countdown fade from the button and picker fade
+- Fixed the nested animation callback syntax after adding the countdown fade-out flow
+
+The goal is for the countdown text to fade out first, then for the button and adjacent timer values to fade back in after the countdown is gone.
+
+---
+
+## #LearningNotes
+
+### #ReactState
+Worked with several pieces of state:
+- `duration` stores the selected timer value
+- `isRunning` tracks whether the timer is active
+- `timeRemaining` stores the countdown value
+- `selectedIndex` identifies which duration is selected
+- `showCountdownText` controls whether the selected item renders as `MM:SS`
+- `containerHeight` stores the measured height of the timer screen
+
+Important distinction:
+- State values trigger re-renders when changed
+- Animated values drive smooth visual changes without normal React state updates on every animation frame
+
+---
+
+### #Hooks
+Clarified where hooks are allowed:
+- `useState`, `useRef`, `useEffect`, and `useCallback` must be called inside the component
+- Hooks must not be placed inside callbacks, conditionals, loops, or event handlers
+- `useEffect` dependency arrays must be inside the `useEffect(...)` call
+
+One key bug came from an effect without a proper dependency array. Because the countdown updates state every second, the effect ran every second and reset the red overlay position.
+
+---
+
+### #AnimationFlow
+The timer now uses multiple animated values:
+- `timerAnimation` controls the red overlay movement
+- `buttonAnimation` controls the start button and inactive timer value visibility
+- `taskDetailsAnimation` controls the placeholder task information
+- `countdownAnimation` controls the `MM:SS` countdown visibility
+
+The main lesson was that one animation value should not control too many unrelated visual states. Separate animation values make it easier to control the order of fade-out and fade-in transitions.
+
+---
+
+## #Verification
+
+The timer file syntax issue around the end of the `animation` callback was fixed.
+
+Current lint result:
+
+```text
+npm run lint
+exited successfully
+```
+
+The previous parse error was caused by mismatched closing braces/parentheses near the nested `.start(...)` callbacks at the end of the animation sequence.
+
+The remaining behavior to confirm is the final transition order:
+- `MM:SS` countdown should fade out
+- selected text should switch back to the normal timer value while hidden
+- adjacent timer values should fade back in
+- start button should fade back in
+
+---
+
+## #FilesChanged
+
+Main file worked on:
+
+```text
+app/(tabs)/timer.tsx
+```
+
+New note added:
+
+```text
+notes/work-report-timer-2026-04-22.md
+```
+
+---
+
+## #Conclusion
+
+The timer UI moved from a basic animated duration selector toward a more complete timer experience. It now has placeholder task information, a `MM:SS` countdown concept, measured layout support, and separate animation values for different UI elements.
+
+The syntax error at the end of the animation callback has been fixed and lint now passes. The remaining immediate work is to finish confirming the final fade-out/fade-in ordering so the countdown disappears cleanly before the picker and start button return.
diff --git a/notes/work-report-timer-2026-04-23.md b/notes/work-report-timer-2026-04-23.md
new file mode 100644
index 0000000..6e9df8c
--- /dev/null
+++ b/notes/work-report-timer-2026-04-23.md
@@ -0,0 +1,141 @@
+# Timer Interaction and Cancel Flow Work Report
+
+## #Overview
+Today the standalone timer screen was developed further with a focus on the cancel interaction, countdown reset order, and a progress cue inside the cancel button.
+
+The main work was not just adding UI pieces, but understanding how the existing React Native `Animated` flow behaves when a timer is started, cancelled, or allowed to finish naturally. The timer is still being treated as its own tab with placeholder task information, but the interaction model is now closer to the intended study-session behavior.
+
+---
+
+## #ImplementedFeatures
+
+### #CancelButton
+Added a dedicated cancel control for the active timer state:
+- Added a separate cancel button animation value
+- Added a bottom-positioned cancel button that appears only during the running state
+- Added reverse handling so the button can be dismissed again when cancelling manually or when the timer finishes
+
+The main goal was to keep the original large start control as the primary entry point, while giving the active timer state its own secondary exit action.
+
+---
+
+### #CancelProgressCue
+Started adding a progress cue directly inside the cancel button:
+- Added a separate `cancelProgressAnimation`
+- Added an inner animated fill layer inside the cancel button
+- Changed the progress direction to move left-to-right inside the button instead of using a full-button opacity fade
+
+This was done to match the visual language of the main red timer overlay while keeping the progress indicator smaller and more local to the cancel action.
+
+---
+
+### #DurationLocking
+Updated the duration selector to stay fixed while the timer is running:
+- Added `scrollEnabled={!timerIsRunning}` to the horizontal timer picker
+- Added an early return inside `onMomentumScrollEnd`
+- Prevented the selected timer duration from changing once a session has started
+
+This keeps the timer state consistent after the session begins and avoids the picker drifting into a visually different value while the countdown is active.
+
+---
+
+### #CountdownOwnership
+Clarified how the countdown interval should be owned and reset:
+- Added `countdownRef`
+- Added interval clearing before starting a new countdown
+- Used the ref-based interval handle so cancel and finish logic can target the active countdown
+
+This work was needed because countdown behavior becomes unreliable if the code starts new intervals without keeping a consistent reference to the currently running one.
+
+---
+
+### #CancelFlowSequencing
+Worked on the ordering of reverse animations during manual cancel:
+- Tested separating countdown fade-out from the picker/start-button return
+- Investigated why adjacent numbers were reappearing before the countdown text had fully finished reversing
+- Traced the problem to both animation timing and the `showCountdownText` render condition
+
+The important lesson here was that hiding the countdown visually and switching the rendered text back to the normal timer value are related, but not identical, events.
+
+---
+
+## #LearningNotes
+
+### #AnimatedValueResponsibilities
+Today reinforced that each `Animated.Value` should have one clear responsibility:
+- `timerAnimation` controls the red overlay position
+- `buttonAnimation` controls start-button disappearance and inactive picker return
+- `countdownAnimation` controls countdown visibility
+- `cancelButtonAnimation` controls the cancel button itself
+- `cancelProgressAnimation` controls the left-to-right fill inside the cancel button
+
+Several visual bugs came from trying to make one animated value carry two different meanings at the same time.
+
+---
+
+### #RenderStateVsAnimationState
+A key distinction became clearer during the cancel-flow debugging:
+- Animated values control motion and opacity
+- Regular React state controls what text/content is actually rendered
+
+One important example is `showCountdownText`:
+- Even if the countdown has visually faded out, the selected timer item still renders `MM:SS` while `showCountdownText` remains `true`
+- This means the UI can still appear to be in “countdown mode” even after part of the reverse animation has already completed
+
+This is why some cancel-order issues were not purely animation problems.
+
+---
+
+### #SequenceVsParallel
+The timer work also clarified when `Animated.sequence([...])` and `Animated.parallel([...])` should be used:
+- `sequence` is for strict order
+- `parallel` is for animations that should run at the same time
+
+One mistake that surfaced during the progress-button work was placing the long progress-fill animation in a sequence after the main timer animation, which caused the fill to begin only after the timer had already ended.
+
+---
+
+## #CurrentIssue
+
+The current timer screen still has remaining cancel-flow polish issues around visual timing and overlay cleanup.
+
+The main issue currently under investigation is the reset order during manual cancel:
+- the red timer overlay can still produce a visible flash/jump when the running animation is interrupted
+- the adjacent picker numbers and selected countdown text are sensitive to both animation order and `showCountdownText`
+- the current implementation needs further refinement so cancel feels deliberate instead of visually noisy
+
+Current lint result:
+
+```text
+npm run lint
+completed with 1 warning
+```
+
+Current warning:
+- unnecessary `showCountdownText` dependency in one `useCallback`
+
+There are no current lint errors, but the cancel interaction is not yet considered visually finished.
+
+---
+
+## #FilesChanged
+
+Main file worked on:
+
+```text
+app/(tabs)/timer.tsx
+```
+
+New note added:
+
+```text
+notes/work-report-timer-2026-04-23.md
+```
+
+---
+
+## #Conclusion
+
+The timer screen moved further toward a complete active-session interaction today. It now has a dedicated cancel control, a left-to-right progress cue inside that control, a locked duration picker while running, and a clearer separation between countdown ownership and animation ownership.
+
+The main remaining work is not basic feature addition, but interaction polish. In particular, the cancel sequence still needs refinement so the red overlay, countdown text, and adjacent timer values return in a clean and intentional order.
diff --git a/notes/work-report-timer-2026-04-24.md b/notes/work-report-timer-2026-04-24.md
new file mode 100644
index 0000000..317dc5c
--- /dev/null
+++ b/notes/work-report-timer-2026-04-24.md
@@ -0,0 +1,191 @@
+# Timer Focus Mode and Hold-Cancel Work Report
+
+## #Overview
+Today the standalone timer screen was reworked further with a focus on the active sprint layout, countdown ownership, and the hold-to-cancel interaction.
+
+The main direction was to make the running timer feel more like a focused study state instead of a duration picker that happens to count down. The countdown was moved toward a separate overlay, the task details were given more visual emphasis, and the cancel interaction was changed from a simple button press into a deliberate hold action.
+
+---
+
+## #ImplementedFeatures
+
+### #CountdownOverlay
+Moved the active countdown away from the duration picker:
+- Removed the old selected-picker countdown state
+- Added a separate countdown overlay using `countdownAnimation`
+- Added `focusModeAnimation` so the countdown can move from the central timer area toward the upper-left area
+- Kept the picker responsible for duration values only
+
+This separates two responsibilities that had previously been mixed together: the picker selects a duration, while the overlay shows active countdown time.
+
+---
+
+### #FocusModeLayout
+Adjusted the active timer layout to put more attention on the task:
+- Moved task details higher and closer to the center of the running screen
+- Increased the task title and description size
+- Kept task details animated through `taskDetailsAnimation`
+- Continued using the red screen overlay as the main visual timer-progress element
+
+The intent is for the active state to feel more like a study-session spotlight, where the selected task becomes the main focus and the countdown becomes supporting information.
+
+---
+
+### #HoldToCancel
+Changed the cancel action into a hold interaction:
+- Added `HOLD_TO_CANCEL_MS`
+- Added `cancelHoldTimeoutRef`
+- Added a hold-completion haptic warning
+- Kept the cancel button scale feedback during press
+- Changed the label to `Hold to end sprint`
+
+This makes cancellation more deliberate and reduces the chance of accidentally ending a sprint with a single tap.
+
+---
+
+### #CancelAccelerationExperiment
+Implemented the red timer overlay as cancel feedback:
+- Added delayed cancel acceleration through `CANCEL_ANIMATION_DELAY_MS`
+- Added `cancelHoldAnimationDelayRef`
+- Added `cancelAccelStartedRef` to distinguish quick taps from actual hold acceleration
+- Split normal timer progress into `progressAnimationRef`
+- Added `startProgressAnimation(fromY)` so progress can start or resume from a specific overlay position
+- Added `cancelOverlayAnimation` as a temporary visual offset on top of the real timer progress
+- Added `getCancelOverlayTarget(...)` to calculate how far the cancel preview should move
+- Added a release handoff animation so the cancel offset eases back into the real timer position
+- Added clamping so the visual overlay does not move past the finished timer position
+- Added easing constants for the cancel delay, release handoff, and timer reset timings
+
+The goal was for the red overlay to speed toward the finished position during a hold, then return smoothly to the real timer progress if the user releases before the cancel completes. The important change is that cancel preview motion is now layered on top of the real progress instead of directly taking over the main timer animation.
+
+---
+
+### #DurationPickerCleanup
+Cleaned up the duration picker after moving countdown ownership out of it:
+- Removed selected countdown rendering from the picker item
+- Kept picker items rendering plain timer values
+- Kept picker values fading out during active timer mode
+- Added index clamping when reading the selected duration from `onMomentumScrollEnd`
+- Restored `duration` as a dependency of the start callback so the selected picker value is used correctly
+
+This fixed the earlier issue where the timer could behave as if the selected duration was still the initial value.
+
+---
+
+### #TimerCodeCleanup
+Cleaned up the timer screen structure after the interaction behavior was stabilized:
+- Renamed the old `animation` callback to `startTimer`
+- Renamed unclear animated values like `opacity` and `translateY` to `startButtonOpacity` and `startButtonTranslateY`
+- Grouped refs by purpose: animated values, timer/session refs, and cancel-hold refs
+- Extracted `clearCountdown`, `clearCancelHoldTimers`, and `stopTimerAnimations`
+- Extracted the cancel overlay target calculation into `getCancelOverlayTarget(...)`
+- Split the render section into local render helpers for the overlay, start button, cancel button, countdown, duration picker, and task details
+- Moved the timer item layout into `styles.timerItem`
+
+This did not change the screen into a separate hook or split the timer into multiple files. The cleanup stayed local to `timer.tsx` so the current animation work remains easy to inspect.
+
+---
+
+## #LearningNotes
+
+### #AnimationOwnership
+The main lesson today was that an `Animated.Value` should have one clear owner at a time.
+
+The red overlay now combines two animated values:
+- normal timer progress
+- hold-to-cancel visual offset
+
+The normal timer progress is controlled by `timerAnimation`, while cancel preview motion is controlled by `cancelOverlayAnimation`. This avoids stopping the real timer progress just to show the cancel speed-up effect.
+
+---
+
+### #RefsAsMutableState
+Several refs were added to track animation and timer ownership:
+- `progressAnimationRef` tracks the long-running red overlay progress animation
+- `sessionStartedAtRef` tracks the progress timeline used for recovery calculations
+- `sessionDurationMsRef` stores the current timer duration in milliseconds
+- `cancelHoldTimeoutRef` tracks when hold cancellation should complete
+- `cancelHoldAnimationDelayRef` tracks when cancel acceleration should begin
+- `cancelAccelStartedRef` tracks whether the red overlay acceleration actually started
+- `cancelHoldActiveRef` and `cancelHoldIdRef` prevent stale delayed hold callbacks from taking over after release
+
+The important distinction is that assigning to `.current` is allowed even when the ref variable itself is declared with `const`.
+
+---
+
+### #CancelOffsetHandoff
+The release recovery logic was changed to avoid rewriting the real timer progress:
+- keep `timerAnimation` running as the source of real timer progress
+- add `cancelOverlayAnimation` on top of it while the cancel button is held
+- animate only the cancel offset back to `0` when the hold is released
+- keep the visible overlay clamped to the screen height
+- tune the release handoff timing with `CANCEL_RELEASE_MS`
+
+This makes the visual red overlay return to the countdown's real timer position without forcing the main timer animation to stop and restart.
+
+---
+
+## #CurrentState
+
+The hold-cancel red overlay interaction has been reworked so the cancel preview no longer directly mutates the real timer progress.
+
+The current implementation:
+- keeps the countdown and real timer progress owned by `timerAnimation`
+- uses `cancelOverlayAnimation` as a temporary visual offset during hold-to-cancel
+- invalidates stale hold callbacks with `cancelHoldIdRef`
+- eases the cancel offset back to `0` on release
+- keeps the cancel-completion path separate from normal timer completion
+
+This should make the red overlay speed-up feel connected to the cancel hold while still keeping the timer progress visually aligned with the countdown after release.
+
+---
+
+## #Verification
+
+Current static checks pass:
+
+```text
+npm run lint
+exited successfully
+```
+
+```text
+npx tsc --noEmit
+exited successfully
+```
+
+The hold-cancel handoff was also adjusted based on runtime feedback so the cancel offset eases back more smoothly into the real timer progress.
+
+---
+
+## #FilesChanged
+
+Main file worked on:
+
+```text
+app/(tabs)/timer.tsx
+```
+
+New note added:
+
+```text
+notes/work-report-timer-2026-04-24.md
+```
+
+---
+
+## #Conclusion
+
+The timer screen moved further toward a focused active-sprint experience. The countdown is now separated from the duration picker, task details have more visual weight, and cancel is treated as a deliberate hold action rather than a normal tap.
+
+The main animation change is that hold-to-cancel now keeps the real timer progress separate from the temporary cancel speed-up effect. The code was also cleaned up so the timer flow is easier to read and continue working on.
+
+## Problems occuring after writing conclusion
+Tried to implement sound by installing expo-audio. This caused the dependency list to update. The diff was massive, and something in the diff caused the entire timer page to break. Logic, animations - the lot. Have reverted back to last known working dependency list, as well as un-refactored a lot of code in an attempt to revert to a functioning state before figuring out that the culprit was dependencies. Need to figure our what is causing the critical failure in the new list.
+
+## Todo
+- Re-refactor to make code cleaner, more readable and easier to maintain.
+- Figure out the dependency issues of later dependency lists
+
+## Conclusion of dependecy saga
+There was a mismatch in the nativewind dependency, with my one being ^4.2.3 and the other list being ^4.1.23. This cause my entire timer screen to fail. Animations got borked, buttons not working properly, duration picker only showing 2 indexes... the works. Solution - keepp nativewind dependency to ^4.2.3
diff --git a/notes/work-report-timer-2026-04-25.md b/notes/work-report-timer-2026-04-25.md
new file mode 100644
index 0000000..eb8d9a0
--- /dev/null
+++ b/notes/work-report-timer-2026-04-25.md
@@ -0,0 +1,163 @@
+# Timer Refactor and Verification Work Report
+
+## #Overview
+Today the timer screen was worked on with a narrower goal than yesterday: not new interaction features, but cleanup, readability, and making the existing timer flow easier to understand and maintain.
+
+This follows directly from yesterday's state. The April 24 note ended with two follow-up items:
+- re-refactor the timer code so it becomes easier to read and work on
+- keep the dependency situation stable after the NativeWind version mismatch had broken the screen
+
+Today's work focused on the first of those. The interaction model was kept the same, but the internal structure of `timer.tsx` was cleaned up so the current hold-to-cancel and focus-mode behavior is easier to inspect without splitting the code into hooks or separate files.
+
+---
+
+## #ImplementedFeatures
+
+### #TimerCodeRefactor
+Refactored the timer screen structure inside `app/(tabs)/timer.tsx`:
+- renamed the component from `App` to `TimerScreen`
+- renamed unclear callbacks such as the old generic start-animation function into `startTimerSession`
+- grouped the file more clearly into constants, animated values, refs, derived values, actions, render helpers, and JSX
+- renamed vague animated/interpolated values to clearer names such as `startButtonOpacity`, `startButtonTranslateY`, and `pickerOpacity`
+
+This did not change the screen architecture into multiple files. The cleanup stayed local to the timer file so the animation flow is still easy to inspect in one place.
+
+---
+
+### #CleanupHelpers
+Extracted repeated timer cleanup work into small local helpers:
+- added `clearCountdownInterval()`
+- added `clearCancelHoldTimeouts()`
+- added `stopRunningAnimations()`
+- added `resetSessionValues()`
+
+Before this, the same interval, timeout, and animation-reset work was spread across multiple callbacks. Pulling it into helpers makes it easier to follow what happens when a session starts, finishes, or is cancelled.
+
+---
+
+### #RenderStructureCleanup
+Cleaned up the render section so it is easier to read:
+- moved repeated inline layout styles into named `StyleSheet` entries
+- extracted the timer picker item rendering into a local `renderTimerItem(...)` helper
+- kept the JSX order aligned with the visible screen layers: overlay, start button, cancel button, countdown, duration picker, and task details
+
+This mainly improves scanning. The old file worked, but the render section made you jump between inline style objects and animation expressions to understand each layer.
+
+---
+
+### #CommentAndNamingPass
+Added a small number of comments only where the code was genuinely hard to follow:
+- clarified that `timerAnimation` owns real timer progress
+- clarified that `cancelOverlayAnimation` is only a temporary visual offset during hold-to-cancel
+- clarified that `startProgressAnimation(fromY)` resumes overlay progress from the current Y position
+- clarified why cancel acceleration starts after a short delay
+
+The aim was not to comment every line, but to explain the parts that are hard to infer just by reading the code.
+
+---
+
+### #StateResetTightening
+Made the session cleanup more explicit:
+- reset `sessionStartedAtRef` and `sessionDurationMsRef` when a session ends
+- reset cancel-hold flags during session cleanup
+- made `finishTimer()` explicitly clear the countdown interval before running exit animations
+- kept the existing unmount cleanup so intervals, timeouts, and running animations are not left behind if the screen disappears mid-session
+
+These are small changes, but they make the timer lifecycle more predictable and reduce the amount of stale mutable state left around after finish or cancel paths.
+
+---
+
+## #LearningNotes
+
+### #ReadableCodeVsNewFeatures
+Today's timer work was a good reminder that "more maintainable" does not always mean "more abstract."
+
+For this screen, the right cleanup level was:
+- better names
+- smaller local helpers
+- clearer grouping
+- a few targeted comments
+
+The wrong cleanup level for the current stage would have been moving the logic into extra hooks or files too early, because that would make it harder to inspect the animation flow while the interaction is still being tuned.
+
+---
+
+### #MutableRefOwnership
+The timer file still relies heavily on refs because several parts of the interaction are long-lived and imperative:
+- active countdown interval
+- running start animation
+- running progress animation
+- delayed cancel-preview start
+- hold-to-cancel completion timeout
+
+The cleanup made this easier to see by separating refs that hold animated values from refs that track mutable timer/session ownership.
+
+---
+
+## #CurrentState
+
+Compared with yesterday, the timer interaction model is mostly the same, but the code behind it is more structured.
+
+The current implementation:
+- keeps the red overlay model used yesterday
+- keeps `timerAnimation` as the real progress owner
+- keeps `cancelOverlayAnimation` as the temporary hold-preview layer
+- keeps the delayed hold acceleration and release recovery flow
+- keeps all timer logic local to `timer.tsx`
+- is now easier to read because repeated cleanup and render logic have been extracted into named local pieces
+
+This means today's work was mainly a recovery and consolidation pass after yesterday's interaction-heavy changes and the earlier dependency-related breakage.
+
+---
+
+## #Verification
+
+Today's static checks passed after the refactor:
+
+```text
+npm run lint
+exited successfully
+```
+
+```text
+npx tsc --noEmit
+exited successfully
+```
+
+```text
+git diff --check -- 'app/(tabs)/timer.tsx'
+exited successfully
+```
+
+There was no new timer commit for today at the time of writing this note. The summary above is based on:
+- the current working-tree diff for `app/(tabs)/timer.tsx`
+- the verification commands run after the refactor
+- yesterday's note and timer history for context
+
+I did not do a live Expo interaction test inside this note workflow, so runtime behavior is verified statically plus by code review rather than by manually pressing through the UI.
+
+---
+
+## #FilesChanged
+
+Main file worked on:
+
+```text
+app/(tabs)/timer.tsx
+```
+
+New note added:
+
+```text
+notes/work-report-timer-2026-04-25.md
+```
+
+---
+
+## #Conclusion
+
+The main timer work today was not adding new features, but making yesterday's feature-rich timer implementation is easier to continue working on.
+
+The result is a timer file that keeps the same focus-mode and hold-to-cancel behavior, while being more readable, more structured, and easier to maintain. The biggest improvement is that the important ideas in the file now have clearer names, clearer ownership, and clearer cleanup paths.
+
+The timer is now considered finished and ready to implement into the rest of the project.
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 3df7b70..ec283ad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,7 +30,8 @@
"expo-symbols": "~1.0.8",
"expo-system-ui": "~6.0.9",
"expo-web-browser": "~15.0.10",
- "nativewind": "^4.1.23",
+ "nativewind": "^4.2.3",
+ "patch-package": "^8.0.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
@@ -40,13 +41,19 @@
"react-native-screens": "~4.16.0",
"react-native-url-polyfill": "^3.0.0",
"react-native-web": "~0.21.0",
- "react-native-worklets": "0.5.1"
+ "react-native-worklets": "0.5.1",
+ "tailwindcss": "^3.4.19"
},
"devDependencies": {
+ "@testing-library/react-native": "^13.3.3",
+ "@types/jest": "^30.0.0",
"@types/react": "~19.1.0",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
+ "jest": "^29.7.0",
+ "jest-expo": "^55.0.16",
"patch-package": "^8.0.1",
+ "react-test-renderer": "19.1.0",
"tailwindcss": "^3.4.19",
"typescript": "~5.9.2"
}
@@ -1575,6 +1582,13 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
@@ -2639,6 +2653,88 @@
"node": ">=8"
}
},
+ "node_modules/@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/core/node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@jest/create-cache-key-function": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz",
@@ -2651,6 +2747,16 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz",
+ "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/environment": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
@@ -2666,6 +2772,33 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/@jest/fake-timers": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
@@ -2683,6 +2816,152 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern/node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "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",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "devOptional": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@jest/schemas": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
@@ -2695,6 +2974,53 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/@jest/transform": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
@@ -3521,6 +3847,98 @@
"node": ">=20.0.0"
}
},
+ "node_modules/@testing-library/react-native": {
+ "version": "13.3.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-13.3.3.tgz",
+ "integrity": "sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-matcher-utils": "^30.0.5",
+ "picocolors": "^1.1.1",
+ "pretty-format": "^30.0.5",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "jest": ">=29.0.0",
+ "react": ">=18.2.0",
+ "react-native": ">=0.71",
+ "react-test-renderer": ">=18.2.0"
+ },
+ "peerDependenciesMeta": {
+ "jest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/@sinclair/typebox": {
+ "version": "0.34.49",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
+ "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react-native/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/pretty-format": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
+ "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -3619,6 +4037,217 @@
"@types/istanbul-lib-report": "*"
}
},
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@jest/expect-utils": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz",
+ "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@jest/types": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz",
+ "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==",
+ "dev": true,
+ "license": "MIT",
+ "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": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/@sinclair/typebox": {
+ "version": "0.34.49",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
+ "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jest/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@types/jest/node_modules/ci-info": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@types/jest/node_modules/expect": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz",
+ "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==",
+ "dev": true,
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-message-util": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz",
+ "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@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"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-mock": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz",
+ "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "jest-util": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/jest-util": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz",
+ "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@types/jest/node_modules/pretty-format": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
+ "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsdom": {
+ "version": "20.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+ "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3658,6 +4287,13 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"license": "MIT"
},
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -4288,9 +4924,16 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
- "dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/abab": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+ "deprecated": "Use your platform's native atob() and btoa() methods instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -4328,6 +4971,17 @@
"node": ">=0.4.0"
}
},
+ "node_modules/acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -4338,6 +4992,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.5",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
+ "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -4665,6 +5332,13 @@
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
"license": "MIT"
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -5158,7 +5832,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -5221,6 +5895,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -5304,6 +5988,13 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"license": "MIT"
},
+ "node_modules/cjs-module-lexer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
@@ -5357,6 +6048,24 @@
"node": ">=0.8"
}
},
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -5398,6 +6107,19 @@
"simple-swizzle": "^0.2.2"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -5529,6 +6251,28 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "bin": {
+ "create-jest": "bin/create-jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/cross-fetch": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
@@ -5573,6 +6317,33 @@
"node": ">=4"
}
},
+ "node_modules/cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssstyle": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+ "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssom": "~0.3.6"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cssstyle/node_modules/cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -5580,6 +6351,58 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/data-urls": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/data-urls/node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/data-urls/node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/data-urls/node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -5651,6 +6474,13 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
@@ -5660,6 +6490,21 @@
"node": ">=0.10"
}
},
+ "node_modules/dedent": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
+ "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -5740,6 +6585,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -5768,6 +6623,16 @@
"node": ">=8"
}
},
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -5780,6 +6645,16 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"license": "Apache-2.0"
},
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -5799,6 +6674,30 @@
"node": ">=0.10.0"
}
},
+ "node_modules/domexception": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+ "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/domexception/node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
@@ -5852,6 +6751,19 @@
"integrity": "sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==",
"license": "ISC"
},
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -5867,6 +6779,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/env-editor": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz",
@@ -5876,6 +6801,23 @@
"node": ">=8"
}
},
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/error-ex/node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
@@ -6086,6 +7028,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/escodegen/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/eslint": {
"version": "9.39.4",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
@@ -6497,6 +7472,114 @@
"node": ">=6"
}
},
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/execa/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "devOptional": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/expo": {
"version": "54.0.33",
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz",
@@ -7355,7 +8438,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
- "dev": true,
"license": "Apache-2.0",
"dependencies": {
"micromatch": "^4.0.2"
@@ -7409,6 +8491,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/freeport-async": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz",
@@ -7431,7 +8530,6 @@
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -7584,6 +8682,19 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@@ -7874,6 +8985,26 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+ "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -7903,6 +9034,34 @@
"node": ">= 0.8"
}
},
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
@@ -7916,6 +9075,16 @@
"node": ">= 14"
}
},
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
"node_modules/hyphenate-style-name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
@@ -7931,6 +9100,19 @@
"node": ">=20.0.0"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -8002,6 +9184,26 @@
"node": ">=4"
}
},
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -8011,6 +9213,16 @@
"node": ">=0.8.19"
}
},
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -8306,6 +9518,16 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
@@ -8414,6 +9636,13 @@
"node": ">=8"
}
},
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -8461,6 +9690,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-string": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
@@ -8573,7 +9815,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true,
"license": "MIT"
},
"node_modules/isexe": {
@@ -8607,6 +9848,60 @@
"node": ">=8"
}
},
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -8625,6 +9920,359 @@
"node": ">= 0.4"
}
},
+ "node_modules/jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus/node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus/node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config/node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-config/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "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",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz",
+ "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.3.0",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/@sinclair/typebox": {
+ "version": "0.34.49",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
+ "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-diff/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-diff/node_modules/pretty-format": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
+ "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-environment-jsdom": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz",
+ "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/jsdom": "^20.0.0",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jsdom": "^20.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jest-environment-node": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
@@ -8642,6 +10290,147 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/jest-expo": {
+ "version": "55.0.16",
+ "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-55.0.16.tgz",
+ "integrity": "sha512-bOvrTNyDaiaoTz9GhvnXib9v9rjX9PTJFvvoqRMRKEg4MoHghG82E7YF+pH71EWSXTaibQ07F46GS+fcUxTWEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config": "~55.0.15",
+ "@expo/json-file": "^10.0.13",
+ "@jest/create-cache-key-function": "^29.2.1",
+ "@jest/globals": "^29.2.1",
+ "babel-jest": "^29.2.1",
+ "jest-environment-jsdom": "^29.2.1",
+ "jest-snapshot": "^29.2.1",
+ "jest-watch-select-projects": "^2.0.0",
+ "jest-watch-typeahead": "2.2.1",
+ "json5": "^2.2.3",
+ "lodash": "^4.17.19",
+ "react-test-renderer": "19.2.0",
+ "server-only": "^0.0.1",
+ "stacktrace-js": "^2.0.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "peerDependencies": {
+ "expo": "*",
+ "react-native": "*",
+ "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4"
+ },
+ "peerDependenciesMeta": {
+ "react-server-dom-webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-expo/node_modules/@expo/config": {
+ "version": "55.0.15",
+ "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.15.tgz",
+ "integrity": "sha512-lHc0ELIQ8126jYOMZpLv3WIuvordW98jFg5aT/J1/12n2ycuXu01XLZkJsdw0avO34cusUYb1It+MvY8JiMduA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config-plugins": "~55.0.8",
+ "@expo/config-types": "^55.0.5",
+ "@expo/json-file": "^10.0.13",
+ "@expo/require-utils": "^55.0.4",
+ "deepmerge": "^4.3.1",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "resolve-workspace-root": "^2.0.0",
+ "semver": "^7.6.0",
+ "slugify": "^1.3.4"
+ }
+ },
+ "node_modules/jest-expo/node_modules/@expo/config-plugins": {
+ "version": "55.0.8",
+ "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.8.tgz",
+ "integrity": "sha512-8WfWTRntTCcowfOS+tHdB0z98gKetTwktg4G5TWkCkXVa8Jt1NUnvzaaU4UHk2vbR2U4N84RyZJFizSwfF6C9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@expo/config-types": "^55.0.5",
+ "@expo/json-file": "~10.0.13",
+ "@expo/plist": "^0.5.2",
+ "@expo/sdk-runtime-versions": "^1.0.0",
+ "chalk": "^4.1.2",
+ "debug": "^4.3.5",
+ "getenv": "^2.0.0",
+ "glob": "^13.0.0",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.5.4",
+ "slugify": "^1.6.6",
+ "xcode": "^3.0.1",
+ "xml2js": "0.6.0"
+ }
+ },
+ "node_modules/jest-expo/node_modules/@expo/config-types": {
+ "version": "55.0.5",
+ "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.5.tgz",
+ "integrity": "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-expo/node_modules/@expo/plist": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.2.tgz",
+ "integrity": "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ }
+ },
+ "node_modules/jest-expo/node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jest-expo/node_modules/react-test-renderer": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.0.tgz",
+ "integrity": "sha512-zLCFMHFE9vy/w3AxO0zNxy6aAupnCuLSVOJYDe/Tp+ayGI1f2PLQsFVPANSD42gdSbmYx5oN+1VWDhcXtq7hAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "react-is": "^19.2.0",
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.0"
+ }
+ },
+ "node_modules/jest-expo/node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-expo/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/jest-get-type": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
@@ -8676,6 +10465,91 @@
"fsevents": "^2.3.2"
}
},
+ "node_modules/jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz",
+ "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.3.0",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/@sinclair/typebox": {
+ "version": "0.34.49",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
+ "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-matcher-utils/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/pretty-format": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
+ "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/jest-message-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
@@ -8710,6 +10584,24 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jest-regex-util": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
@@ -8719,6 +10611,260 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve/node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "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",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "devOptional": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/jest-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
@@ -8768,6 +10914,156 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/jest-watch-select-projects": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/jest-watch-select-projects/-/jest-watch-select-projects-2.0.0.tgz",
+ "integrity": "sha512-j00nW4dXc2NiCW6znXgFLF9g8PJ0zP25cpQ1xRro/HU2GBfZQFZD0SoXnAlaoKkIY4MlfTMkKGbNXFpvCdjl1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^4.3.0",
+ "chalk": "^3.0.0",
+ "prompts": "^2.2.1"
+ }
+ },
+ "node_modules/jest-watch-select-projects/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-watch-typeahead": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.1.tgz",
+ "integrity": "sha512-jYpYmUnTzysmVnwq49TAxlmtOAwp8QIqvZyoofQFn8fiWhEDZj33ZXzg3JA4nGnzWFm1hbWf3ADpteUokvXgFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^6.0.0",
+ "chalk": "^4.0.0",
+ "jest-regex-util": "^29.0.0",
+ "jest-watcher": "^29.0.0",
+ "slash": "^5.0.0",
+ "string-length": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "jest": "^27.0.0 || ^28.0.0 || ^29.0.0"
+ }
+ },
+ "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz",
+ "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watch-typeahead/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/jest-watch-typeahead/node_modules/char-regex": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz",
+ "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/jest-watch-typeahead/node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watch-typeahead/node_modules/string-length": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
+ "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^2.0.0",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watch-typeahead/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/jest-worker": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
@@ -8837,6 +11133,138 @@
"integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==",
"license": "0BSD"
},
+ "node_modules/jsdom": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "abab": "^2.0.6",
+ "acorn": "^8.8.1",
+ "acorn-globals": "^7.0.0",
+ "cssom": "^0.5.0",
+ "cssstyle": "^2.3.0",
+ "data-urls": "^3.0.2",
+ "decimal.js": "^10.4.2",
+ "domexception": "^4.0.0",
+ "escodegen": "^2.0.0",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^3.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.2",
+ "parse5": "^7.1.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.2",
+ "w3c-xmlserializer": "^4.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^11.0.0",
+ "ws": "^8.11.0",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "canvas": "^2.5.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/jsdom/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/jsdom/node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jsdom/node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jsdom/node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/jsdom/node_modules/ws": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
+ "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
+ "dev": true,
+ "license": "MIT",
+ "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
+ }
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -8856,6 +11284,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -8867,7 +11302,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
"integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
@@ -8906,7 +11340,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
@@ -8919,7 +11352,6 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
- "dev": true,
"license": "Public Domain",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -8955,7 +11387,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.11"
@@ -9310,6 +11741,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -9433,6 +11871,35 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "devOptional": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -9844,6 +12311,16 @@
"node": ">=4"
}
},
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
@@ -9950,14 +12427,14 @@
}
},
"node_modules/nativewind": {
- "version": "4.1.23",
- "resolved": "https://registry.npmjs.org/nativewind/-/nativewind-4.1.23.tgz",
- "integrity": "sha512-oLX3suGI6ojQqWxdQezOSM5GmJ4KvMnMtmaSMN9Ggb5j7ysFt4nHxb1xs8RDjZR7BWc+bsetNJU8IQdQMHqRpg==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/nativewind/-/nativewind-4.2.3.tgz",
+ "integrity": "sha512-HglF1v6A8CqBFpXWs0d3yf4qQGurrreLuyE8FTRI/VDH8b0npZa2SDG5tviTkLiBg0s5j09mQALZOjxuocgMLA==",
"license": "MIT",
"dependencies": {
"comment-json": "^4.2.5",
"debug": "^4.3.7",
- "react-native-css-interop": "0.1.22"
+ "react-native-css-interop": "0.2.3"
},
"engines": {
"node": ">=16"
@@ -9970,7 +12447,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/negotiator": {
@@ -10084,12 +12561,32 @@
"node": ">=10"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/nullthrows": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
"license": "MIT"
},
+ "node_modules/nwsapi": {
+ "version": "2.2.23",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
+ "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ob1": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz",
@@ -10503,6 +13000,25 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parse-png": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz",
@@ -10515,6 +13031,19 @@
"node": ">=10"
}
},
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -10528,7 +13057,6 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
@@ -10558,7 +13086,6 @@
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -10574,7 +13101,6 @@
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -10587,7 +13113,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -10687,6 +13212,75 @@
"node": ">= 6"
}
},
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@@ -11009,6 +13603,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -11018,6 +13625,23 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/qrcode-terminal": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz",
@@ -11044,6 +13668,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -11219,16 +13850,16 @@
}
},
"node_modules/react-native-css-interop": {
- "version": "0.1.22",
- "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.1.22.tgz",
- "integrity": "sha512-Mu01e+H9G+fxSWvwtgWlF5MJBJC4VszTCBXopIpeR171lbeBInHb8aHqoqRPxmJpi3xIHryzqKFOJYAdk7PBxg==",
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.2.3.tgz",
+ "integrity": "sha512-wc+JI7iUfdFBqnE18HhMTtD0q9vkhuMczToA87UdHGWwMyxdT5sCcNy+i4KInPCE855IY0Ic8kLQqecAIBWz7w==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.22.15",
"@babel/traverse": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.3.7",
- "lightningcss": "^1.27.0",
+ "lightningcss": "~1.27.0",
"semver": "^7.6.3"
},
"engines": {
@@ -11249,6 +13880,258 @@
}
}
},
+ "node_modules/react-native-css-interop/node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz",
+ "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.27.0",
+ "lightningcss-darwin-x64": "1.27.0",
+ "lightningcss-freebsd-x64": "1.27.0",
+ "lightningcss-linux-arm-gnueabihf": "1.27.0",
+ "lightningcss-linux-arm64-gnu": "1.27.0",
+ "lightningcss-linux-arm64-musl": "1.27.0",
+ "lightningcss-linux-x64-gnu": "1.27.0",
+ "lightningcss-linux-x64-musl": "1.27.0",
+ "lightningcss-win32-arm64-msvc": "1.27.0",
+ "lightningcss-win32-x64-msvc": "1.27.0"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-darwin-arm64": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz",
+ "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-darwin-x64": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz",
+ "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-freebsd-x64": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz",
+ "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz",
+ "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz",
+ "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz",
+ "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz",
+ "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz",
+ "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz",
+ "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz",
+ "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/react-native-css-interop/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@@ -11570,6 +14453,20 @@
}
}
},
+ "node_modules/react-test-renderer": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
+ "integrity": "sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "react-is": "^19.1.0",
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -11591,6 +14488,20 @@
"node": ">=8.10.0"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -11734,6 +14645,13 @@
"path-parse": "^1.0.5"
}
},
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "2.0.0-next.6",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz",
@@ -11758,6 +14676,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
@@ -11949,6 +14880,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
@@ -11958,6 +14896,19 @@
"node": ">=11.0.0"
}
},
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@@ -12374,6 +15325,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stack-generator": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
+ "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "stackframe": "^1.3.4"
+ }
+ },
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@@ -12401,6 +15362,39 @@
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"license": "MIT"
},
+ "node_modules/stacktrace-gps": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz",
+ "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "source-map": "0.5.6",
+ "stackframe": "^1.3.4"
+ }
+ },
+ "node_modules/stacktrace-gps/node_modules/source-map": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
+ "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stacktrace-js": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz",
+ "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "error-stack-parser": "^2.0.6",
+ "stack-generator": "^2.0.5",
+ "stacktrace-gps": "^3.0.4"
+ }
+ },
"node_modules/stacktrace-parser": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz",
@@ -12454,6 +15448,20 @@
"node": ">=4"
}
},
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -12588,11 +15596,34 @@
"node": ">=4"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -12681,6 +15712,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -12915,7 +15953,6 @@
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
@@ -12948,6 +15985,32 @@
"node": ">=0.6"
}
},
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tough-cookie/node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -13232,7 +16295,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
@@ -13322,6 +16384,17 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -13420,6 +16493,21 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
"node_modules/validate-npm-package-name": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
@@ -13634,6 +16722,19 @@
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==",
"license": "MIT"
},
+ "node_modules/w3c-xmlserializer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+ "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -13664,12 +16765,36 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
+ "node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
"license": "MIT"
},
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -13892,6 +17017,16 @@
"node": ">=10.0.0"
}
},
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
@@ -13923,6 +17058,13 @@
"node": ">=8.0"
}
},
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index bd126c8..62fa3aa 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,14 @@
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
- "postinstall": "patch-package",
"start": "expo start",
+ "reset-project": "node ./scripts/reset-project.js",
+ "postinstall": "patch-package",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
- "lint": "expo lint"
+ "lint": "expo lint",
+ "test": "jest"
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
@@ -32,7 +34,8 @@
"expo-symbols": "~1.0.8",
"expo-system-ui": "~6.0.9",
"expo-web-browser": "~15.0.10",
- "nativewind": "^4.1.23",
+ "nativewind": "^4.2.3",
+ "patch-package": "^8.0.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
@@ -42,15 +45,24 @@
"react-native-screens": "~4.16.0",
"react-native-url-polyfill": "^3.0.0",
"react-native-web": "~0.21.0",
- "react-native-worklets": "0.5.1"
+ "react-native-worklets": "0.5.1",
+ "tailwindcss": "^3.4.19"
},
"devDependencies": {
+ "@testing-library/react-native": "^13.3.3",
+ "@types/jest": "^30.0.0",
"@types/react": "~19.1.0",
"eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0",
+ "jest": "^29.7.0",
+ "jest-expo": "^55.0.16",
"patch-package": "^8.0.1",
+ "react-test-renderer": "19.1.0",
"tailwindcss": "^3.4.19",
"typescript": "~5.9.2"
},
- "private": true
-}
+ "private": true,
+ "jest": {
+ "preset": "jest-expo"
+ }
+}
\ No newline at end of file