refactor: separate react/react-native
This commit is contained in:
parent
748ac2476c
commit
f3156eee61
@ -1,7 +1,7 @@
|
|||||||
import { Redirect, Tabs } from "expo-router"
|
import { Redirect, Tabs } from "expo-router"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
import { TabBarIcon } from "@/presentation/react/components/TabBarIcon"
|
import { TabBarIcon } from "@/presentation/react-native/ui/TabBarIcon"
|
||||||
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
||||||
|
|
||||||
const TabLayout: React.FC = () => {
|
const TabLayout: React.FC = () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Redirect, useLocalSearchParams } from "expo-router"
|
import { Redirect, useLocalSearchParams } from "expo-router"
|
||||||
|
|
||||||
import { HabitEditForm } from "@/presentation/react/components/HabitForm/HabitEditForm"
|
import { HabitEditForm } from "@/presentation/react-native/components/HabitForm/HabitEditForm"
|
||||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||||
|
|
||||||
const HabitPage: React.FC = () => {
|
const HabitPage: React.FC = () => {
|
||||||
|
@ -4,11 +4,11 @@ import { Agenda } from "react-native-calendars"
|
|||||||
import { Text } from "react-native-paper"
|
import { Text } from "react-native-paper"
|
||||||
import { SafeAreaView } from "react-native-safe-area-context"
|
import { SafeAreaView } from "react-native-safe-area-context"
|
||||||
|
|
||||||
import { getISODate, getNowDate } from "@/utils/dates"
|
import { getISODate, getNowDateUTC } from "@/utils/dates"
|
||||||
|
|
||||||
const HistoryPage: React.FC = () => {
|
const HistoryPage: React.FC = () => {
|
||||||
const today = useMemo(() => {
|
const today = useMemo(() => {
|
||||||
return getNowDate()
|
return getNowDateUTC()
|
||||||
}, [])
|
}, [])
|
||||||
const todayISO = getISODate(today)
|
const todayISO = getISODate(today)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { SafeAreaView } from "react-native-safe-area-context"
|
import { SafeAreaView } from "react-native-safe-area-context"
|
||||||
import { ActivityIndicator, Button, Text } from "react-native-paper"
|
import { ActivityIndicator, Button, Text } from "react-native-paper"
|
||||||
|
|
||||||
import { HabitsMainPage } from "@/presentation/react/components/HabitsMainPage/HabitsMainPage"
|
import { HabitsMainPage } from "@/presentation/react-native/components/HabitsMainPage/HabitsMainPage"
|
||||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||||
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HabitCreateForm } from "@/presentation/react/components/HabitForm/HabitCreateForm"
|
import { HabitCreateForm } from "@/presentation/react-native/components/HabitForm/HabitCreateForm"
|
||||||
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
||||||
|
|
||||||
const NewHabitPage: React.FC = () => {
|
const NewHabitPage: React.FC = () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Redirect, Tabs } from "expo-router"
|
import { Redirect, Tabs } from "expo-router"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
import { TabBarIcon } from "@/presentation/react/components/TabBarIcon"
|
import { TabBarIcon } from "@/presentation/react-native/ui/TabBarIcon"
|
||||||
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
import { useAuthentication } from "@/presentation/react/contexts/Authentication"
|
||||||
|
|
||||||
const TabLayout: React.FC = () => {
|
const TabLayout: React.FC = () => {
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { createClient } from "@supabase/supabase-js"
|
import {
|
||||||
|
createClient,
|
||||||
|
type User as SupabaseUserType,
|
||||||
|
} from "@supabase/supabase-js"
|
||||||
import { AppState, Platform } from "react-native"
|
import { AppState, Platform } from "react-native"
|
||||||
import "react-native-url-polyfill/auto"
|
import "react-native-url-polyfill/auto"
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage"
|
import AsyncStorage from "@react-native-async-storage/async-storage"
|
||||||
|
|
||||||
import type { Database } from "./supabase-types"
|
import type { Database } from "./supabase-types"
|
||||||
|
|
||||||
|
export type SupabaseUser = SupabaseUserType
|
||||||
|
export type SupabaseHabit = Database["public"]["Tables"]["habits"]["Row"]
|
||||||
|
export type SupabaseHabitProgress =
|
||||||
|
Database["public"]["Tables"]["habits_progresses"]["Row"]
|
||||||
|
|
||||||
const SUPABASE_URL =
|
const SUPABASE_URL =
|
||||||
process.env["EXPO_PUBLIC_SUPABASE_URL"] ??
|
process.env["EXPO_PUBLIC_SUPABASE_URL"] ??
|
||||||
"https://wjtwtzxreersqfvfgxrz.supabase.co"
|
"https://wjtwtzxreersqfvfgxrz.supabase.co"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"preset": "jest-expo",
|
"preset": "jest-expo",
|
||||||
"roots": ["./"],
|
"roots": ["./"],
|
||||||
"setupFilesAfterEnv": ["@testing-library/react-native/extend-expect"],
|
"setupFilesAfterEnv": ["<rootDir>/tests/setup.ts"],
|
||||||
"fakeTimers": {
|
"fakeTimers": {
|
||||||
"enableGlobally": true
|
"enableGlobally": true
|
||||||
},
|
},
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"coverageReporters": ["text", "text-summary", "cobertura"],
|
"coverageReporters": ["text", "text-summary", "cobertura"],
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"<rootDir>/**/*.{ts,tsx}",
|
"<rootDir>/**/*.{ts,tsx}",
|
||||||
"!<rootDir>/presentation/react/components/ExternalLink.tsx",
|
"!<rootDir>/presentation/react-native/ui/ExternalLink.tsx",
|
||||||
"!<rootDir>/.expo",
|
"!<rootDir>/.expo",
|
||||||
"!<rootDir>/app/+html.tsx",
|
"!<rootDir>/app/+html.tsx",
|
||||||
"!<rootDir>/app/**/_layout.tsx",
|
"!<rootDir>/app/**/_layout.tsx",
|
||||||
|
@ -24,8 +24,8 @@ import { GOAL_FREQUENCIES, GOAL_TYPES } from "@/domain/entities/Goal"
|
|||||||
import type { HabitCreateData } from "@/domain/entities/Habit"
|
import type { HabitCreateData } from "@/domain/entities/Habit"
|
||||||
import { HabitCreateSchema } from "@/domain/entities/Habit"
|
import { HabitCreateSchema } from "@/domain/entities/Habit"
|
||||||
import type { User } from "@/domain/entities/User"
|
import type { User } from "@/domain/entities/User"
|
||||||
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||||
import { useBoolean } from "../../hooks/useBoolean"
|
import { useBoolean } from "@/presentation/react/hooks/useBoolean"
|
||||||
import { IconSelectorModal } from "./IconSelectorModal"
|
import { IconSelectorModal } from "./IconSelectorModal"
|
||||||
|
|
||||||
export interface HabitCreateFormProps {
|
export interface HabitCreateFormProps {
|
||||||
@ -37,11 +37,10 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
formState: { errors, isValid },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
watch,
|
watch,
|
||||||
|
|
||||||
formState: { errors, isValid },
|
|
||||||
} = useForm<HabitCreateData>({
|
} = useForm<HabitCreateData>({
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
resolver: zodResolver(HabitCreateSchema),
|
resolver: zodResolver(HabitCreateSchema),
|
@ -20,8 +20,8 @@ import ColorPicker, {
|
|||||||
|
|
||||||
import type { Habit, HabitEditData } from "@/domain/entities/Habit"
|
import type { Habit, HabitEditData } from "@/domain/entities/Habit"
|
||||||
import { HabitEditSchema } from "@/domain/entities/Habit"
|
import { HabitEditSchema } from "@/domain/entities/Habit"
|
||||||
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||||
import { useBoolean } from "../../hooks/useBoolean"
|
import { useBoolean } from "@/presentation/react/hooks/useBoolean"
|
||||||
import { IconSelectorModal } from "./IconSelectorModal"
|
import { IconSelectorModal } from "./IconSelectorModal"
|
||||||
|
|
||||||
export interface HabitEditFormProps {
|
export interface HabitEditFormProps {
|
||||||
@ -33,8 +33,8 @@ export const HabitEditForm: React.FC<HabitEditFormProps> = ({ habit }) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
|
||||||
formState: { errors, isValid },
|
formState: { errors, isValid },
|
||||||
|
handleSubmit,
|
||||||
} = useForm<HabitEditData>({
|
} = useForm<HabitEditData>({
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
resolver: zodResolver(HabitEditSchema),
|
resolver: zodResolver(HabitEditSchema),
|
@ -3,7 +3,7 @@ import { Agenda } from "react-native-calendars"
|
|||||||
|
|
||||||
import { GOAL_FREQUENCIES } from "@/domain/entities/Goal"
|
import { GOAL_FREQUENCIES } from "@/domain/entities/Goal"
|
||||||
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
||||||
import { getISODate, getNowDate } from "@/utils/dates"
|
import { getISODate, getNowDateUTC } from "@/utils/dates"
|
||||||
import { HabitsEmpty } from "./HabitsEmpty"
|
import { HabitsEmpty } from "./HabitsEmpty"
|
||||||
import { HabitsList } from "./HabitsList"
|
import { HabitsList } from "./HabitsList"
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export interface HabitsMainPageProps {
|
|||||||
export const HabitsMainPage: React.FC<HabitsMainPageProps> = (props) => {
|
export const HabitsMainPage: React.FC<HabitsMainPageProps> = (props) => {
|
||||||
const { habitsTracker } = props
|
const { habitsTracker } = props
|
||||||
|
|
||||||
const today = getNowDate()
|
const today = getNowDateUTC()
|
||||||
const todayISO = getISODate(today)
|
const todayISO = getISODate(today)
|
||||||
|
|
||||||
const [selectedDate, setSelectedDate] = useState<Date>(today)
|
const [selectedDate, setSelectedDate] = useState<Date>(today)
|
@ -1,6 +1,6 @@
|
|||||||
import renderer from "react-test-renderer"
|
import renderer from "react-test-renderer"
|
||||||
|
|
||||||
import { ExternalLink } from "@/presentation/react/components/ExternalLink"
|
import { ExternalLink } from "@/presentation/react-native/ui/ExternalLink"
|
||||||
|
|
||||||
describe("<ExternalLink />", () => {
|
describe("<ExternalLink />", () => {
|
||||||
it("renders correctly", () => {
|
it("renders correctly", () => {
|
@ -1,6 +1,6 @@
|
|||||||
import renderer from "react-test-renderer"
|
import renderer from "react-test-renderer"
|
||||||
|
|
||||||
import { TabBarIcon } from "@/presentation/react/components/TabBarIcon"
|
import { TabBarIcon } from "@/presentation/react-native/ui/TabBarIcon"
|
||||||
|
|
||||||
describe("<TabBarIcon />", () => {
|
describe("<TabBarIcon />", () => {
|
||||||
it("renders correctly", () => {
|
it("renders correctly", () => {
|
@ -2,8 +2,8 @@ import { act, renderHook } from "@testing-library/react-native"
|
|||||||
|
|
||||||
import { useBoolean } from "@/presentation/react/hooks/useBoolean"
|
import { useBoolean } from "@/presentation/react/hooks/useBoolean"
|
||||||
|
|
||||||
describe("hooks/useBoolean", () => {
|
describe("presentation/react/hooks/useBoolean", () => {
|
||||||
beforeEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -11,51 +11,76 @@ describe("hooks/useBoolean", () => {
|
|||||||
|
|
||||||
for (const initialValue of initialValues) {
|
for (const initialValue of initialValues) {
|
||||||
it(`should set the initial value to ${initialValue}`, () => {
|
it(`should set the initial value to ${initialValue}`, () => {
|
||||||
|
// Arrange - Given
|
||||||
const { result } = renderHook(() => {
|
const { result } = renderHook(() => {
|
||||||
return useBoolean({ initialValue })
|
return useBoolean({ initialValue })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
expect(result.current.value).toBe(initialValue)
|
expect(result.current.value).toBe(initialValue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should by default set the initial value to false", () => {
|
it("should by default set the initial value to false", () => {
|
||||||
|
// Arrange - Given
|
||||||
const { result } = renderHook(() => {
|
const { result } = renderHook(() => {
|
||||||
return useBoolean()
|
return useBoolean()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
expect(result.current.value).toBe(false)
|
expect(result.current.value).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should toggle the value", async () => {
|
it("should toggle the value", async () => {
|
||||||
|
// Arrange - Given
|
||||||
const { result } = renderHook(() => {
|
const { result } = renderHook(() => {
|
||||||
return useBoolean({ initialValue: false })
|
return useBoolean({ initialValue: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Act - When
|
||||||
await act(() => {
|
await act(() => {
|
||||||
return result.current.toggle()
|
return result.current.toggle()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
expect(result.current.value).toBe(true)
|
expect(result.current.value).toBe(true)
|
||||||
|
|
||||||
|
// Act - When
|
||||||
await act(() => {
|
await act(() => {
|
||||||
return result.current.toggle()
|
return result.current.toggle()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
expect(result.current.value).toBe(false)
|
expect(result.current.value).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should set the value to true", async () => {
|
it("should set the value to true", async () => {
|
||||||
|
// Arrange - Given
|
||||||
const { result } = renderHook(() => {
|
const { result } = renderHook(() => {
|
||||||
return useBoolean({ initialValue: false })
|
return useBoolean({ initialValue: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Act - When
|
||||||
await act(() => {
|
await act(() => {
|
||||||
return result.current.setTrue()
|
return result.current.setTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
expect(result.current.value).toBe(true)
|
expect(result.current.value).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should set the value to false", async () => {
|
it("should set the value to false", async () => {
|
||||||
|
// Arrange - Given
|
||||||
const { result } = renderHook(() => {
|
const { result } = renderHook(() => {
|
||||||
return useBoolean({ initialValue: true })
|
return useBoolean({ initialValue: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Act - When
|
||||||
await act(() => {
|
await act(() => {
|
||||||
return result.current.setFalse()
|
return result.current.setFalse()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
expect(result.current.value).toBe(false)
|
expect(result.current.value).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
75
presentation/react/hooks/__tests__/usePresenterState.test.ts
Normal file
75
presentation/react/hooks/__tests__/usePresenterState.test.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { act, renderHook } from "@testing-library/react-native"
|
||||||
|
|
||||||
|
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||||
|
import { Presenter } from "@/presentation/presenters/_Presenter"
|
||||||
|
|
||||||
|
interface MockCountPresenterState {
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockCountPresenter extends Presenter<MockCountPresenterState> {
|
||||||
|
public constructor(initialState: MockCountPresenterState) {
|
||||||
|
super(initialState)
|
||||||
|
}
|
||||||
|
|
||||||
|
public increment(): void {
|
||||||
|
this.setState((state) => {
|
||||||
|
state.count = state.count + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("presentation/react/hooks/usePresenterState", () => {
|
||||||
|
it("should return the initial state from the presenter", async () => {
|
||||||
|
// Arrange - Given
|
||||||
|
const initialState = { count: 0 }
|
||||||
|
const presenter = new MockCountPresenter(initialState)
|
||||||
|
|
||||||
|
// Act - When
|
||||||
|
const { result } = renderHook(() => {
|
||||||
|
return usePresenterState(presenter)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
|
expect(result.current).toEqual(initialState)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update state when presenter state changes", async () => {
|
||||||
|
// Arrange - Given
|
||||||
|
const initialState = { count: 0 }
|
||||||
|
const presenter = new MockCountPresenter(initialState)
|
||||||
|
const subscribe = jest.spyOn(presenter, "subscribe")
|
||||||
|
const { result } = renderHook(() => {
|
||||||
|
return usePresenterState(presenter)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Act - When
|
||||||
|
await act(() => {
|
||||||
|
presenter.increment()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
|
expect(result.current.count).toBe(1)
|
||||||
|
expect(subscribe).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should unsubscribe from presenter on unmount", async () => {
|
||||||
|
// Arrange - Given
|
||||||
|
const initialState = { count: 0 }
|
||||||
|
const presenter = new MockCountPresenter(initialState)
|
||||||
|
const unsubscribe = jest.spyOn(presenter, "unsubscribe")
|
||||||
|
const { result, unmount } = renderHook(() => {
|
||||||
|
return usePresenterState(presenter)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Act - When
|
||||||
|
unmount()
|
||||||
|
await act(() => {
|
||||||
|
presenter.increment()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
|
expect(result.current.count).toBe(0)
|
||||||
|
expect(unsubscribe).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
@ -2,9 +2,10 @@ import { useState } from "react"
|
|||||||
|
|
||||||
export interface UseBooleanResult {
|
export interface UseBooleanResult {
|
||||||
value: boolean
|
value: boolean
|
||||||
toggle: () => void
|
setValue: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
setTrue: () => void
|
setTrue: () => void
|
||||||
setFalse: () => void
|
setFalse: () => void
|
||||||
|
toggle: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UseBooleanOptions {
|
export interface UseBooleanOptions {
|
||||||
@ -43,6 +44,7 @@ export const useBoolean = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
|
setValue,
|
||||||
toggle,
|
toggle,
|
||||||
setTrue,
|
setTrue,
|
||||||
setFalse,
|
setFalse,
|
||||||
|
1
tests/setup.ts
Normal file
1
tests/setup.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import "@testing-library/react-native/extend-expect"
|
@ -1,6 +1,12 @@
|
|||||||
import { getISODate, getWeekNumber } from "../dates"
|
import { getISODate, getNowDateUTC, getWeekNumber } from "../dates"
|
||||||
|
|
||||||
describe("utils/dates", () => {
|
describe("utils/dates", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
jest.resetAllMocks()
|
||||||
|
jest.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
describe("getISODate", () => {
|
describe("getISODate", () => {
|
||||||
it("should return the correct date in ISO format (e.g: 2012-05-23)", () => {
|
it("should return the correct date in ISO format (e.g: 2012-05-23)", () => {
|
||||||
// Arrange - Given
|
// Arrange - Given
|
||||||
@ -15,6 +21,25 @@ describe("utils/dates", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("getNowDateUTC", () => {
|
||||||
|
it("should return the current UTC date", () => {
|
||||||
|
// Arrange - Given
|
||||||
|
const mockDate = new Date("2024-05-01T12:00:00Z")
|
||||||
|
jest.useFakeTimers({ now: mockDate })
|
||||||
|
Date.UTC = jest.fn(() => {
|
||||||
|
return mockDate.getTime()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Act - When
|
||||||
|
const result = getNowDateUTC()
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
|
const expected = new Date("2024-05-01T12:00:00.000Z")
|
||||||
|
expect(result).toEqual(expected)
|
||||||
|
expect(Date.UTC).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("getWeekNumber", () => {
|
describe("getWeekNumber", () => {
|
||||||
it("should return the correct week number for a given date (e.g: 2020-01-01)", () => {
|
it("should return the correct week number for a given date (e.g: 2020-01-01)", () => {
|
||||||
// Arrange - Given
|
// Arrange - Given
|
||||||
|
39
utils/__tests__/zod.test.ts
Normal file
39
utils/__tests__/zod.test.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { ZodIssue } from "zod"
|
||||||
|
import { ZodError } from "zod"
|
||||||
|
|
||||||
|
import { getErrorsFieldsFromZodError } from "../zod"
|
||||||
|
|
||||||
|
const zodIssue: ZodIssue = {
|
||||||
|
code: "too_small",
|
||||||
|
minimum: 1,
|
||||||
|
type: "string",
|
||||||
|
inclusive: true,
|
||||||
|
exact: false,
|
||||||
|
message: "String must contain at least 1 character(s)",
|
||||||
|
path: ["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("utils/zod", () => {
|
||||||
|
describe("getErrorsFieldsFromZodError", () => {
|
||||||
|
it("should return an array of the fields that have errors", () => {
|
||||||
|
// Arrange - Given
|
||||||
|
const error = new ZodError([
|
||||||
|
{
|
||||||
|
...zodIssue,
|
||||||
|
path: ["field1"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...zodIssue,
|
||||||
|
path: ["field2"],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// Act - When
|
||||||
|
const result = getErrorsFieldsFromZodError(error)
|
||||||
|
|
||||||
|
// Assert - Then
|
||||||
|
const expected = ["field1", "field2"]
|
||||||
|
expect(result).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -11,7 +11,7 @@ export const getISODate = (date: Date): string => {
|
|||||||
return date.toISOString().slice(0, 10)
|
return date.toISOString().slice(0, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNowDate = (): Date => {
|
export const getNowDateUTC = (): Date => {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
const milliseconds = Date.UTC(
|
const milliseconds = Date.UTC(
|
||||||
date.getFullYear(),
|
date.getFullYear(),
|
||||||
|
@ -3,5 +3,8 @@ import type { ZodError } from "zod"
|
|||||||
export const getErrorsFieldsFromZodError = <T>(
|
export const getErrorsFieldsFromZodError = <T>(
|
||||||
error: ZodError<T>,
|
error: ZodError<T>,
|
||||||
): Array<keyof T> => {
|
): Array<keyof T> => {
|
||||||
return Object.keys(error.format()) as Array<keyof T>
|
const fields = Object.keys(error.format()) as Array<keyof T>
|
||||||
|
return fields.filter((field) => {
|
||||||
|
return field !== "_errors"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user