refactor: mocks data for tests
This commit is contained in:
parent
f3156eee61
commit
06ef8515cb
@ -1,8 +1,8 @@
|
|||||||
import { getISODate, getWeekNumber } from "@/utils/dates"
|
import { getISODate, getWeekNumber } from "@/utils/dates"
|
||||||
import type { Habit } from "./Habit"
|
|
||||||
import type { HabitProgress } from "./HabitProgress"
|
|
||||||
import type { GoalProgress } from "./Goal"
|
import type { GoalProgress } from "./Goal"
|
||||||
import { GoalBooleanProgress, GoalNumericProgress } from "./Goal"
|
import { GoalBooleanProgress, GoalNumericProgress } from "./Goal"
|
||||||
|
import type { Habit } from "./Habit"
|
||||||
|
import type { HabitProgress } from "./HabitProgress"
|
||||||
|
|
||||||
export interface HabitHistoryJSON {
|
export interface HabitHistoryJSON {
|
||||||
habit: Habit
|
habit: Habit
|
||||||
|
106
domain/entities/__tests__/HabitsTracker.test.ts
Normal file
106
domain/entities/__tests__/HabitsTracker.test.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { HABIT_MOCK } from "@/tests/mocks/domain/Habit"
|
||||||
|
import { GOAL_FREQUENCIES } from "../Goal"
|
||||||
|
import { HabitsTracker } from "../HabitsTracker"
|
||||||
|
import { HabitHistory } from "../HabitHistory"
|
||||||
|
import { HABIT_PROGRESS_MOCK } from "@/tests/mocks/domain/HabitProgress"
|
||||||
|
|
||||||
|
describe("domain/entities/HabitsTracker", () => {
|
||||||
|
describe("HabitsTracker.default", () => {
|
||||||
|
for (const frequency of GOAL_FREQUENCIES) {
|
||||||
|
it(`should return empty habitsHistory for ${frequency}`, () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
expect(habitsTracker.habitsHistory[frequency]).toEqual([])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getAllHabitsHistory", () => {
|
||||||
|
it("should return all habits history", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
const habit = HABIT_MOCK.examplesByNames.Walk
|
||||||
|
habitsTracker.addHabit(habit)
|
||||||
|
expect(habitsTracker.getAllHabitsHistory()).toEqual([
|
||||||
|
new HabitHistory({
|
||||||
|
habit,
|
||||||
|
progressHistory: [],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return empty array when no habits are added", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
expect(habitsTracker.getAllHabitsHistory()).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("getHabitHistoryById", () => {
|
||||||
|
it("should return habit history by id", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
const habit = HABIT_MOCK.examplesByNames.Walk
|
||||||
|
habitsTracker.addHabit(habit)
|
||||||
|
expect(habitsTracker.getHabitHistoryById(habit.id)).toEqual(
|
||||||
|
new HabitHistory({
|
||||||
|
habit,
|
||||||
|
progressHistory: [],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return undefined when habit is not found", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
expect(habitsTracker.getHabitHistoryById("invalid-id")).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("addHabit", () => {
|
||||||
|
it("should add habit to habitsHistory", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
const habit = HABIT_MOCK.examplesByNames.Walk
|
||||||
|
habitsTracker.addHabit(habit)
|
||||||
|
expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([
|
||||||
|
new HabitHistory({
|
||||||
|
habit,
|
||||||
|
progressHistory: [],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("editHabit", () => {
|
||||||
|
it("should edit habit in habitsHistory", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
const habit = HABIT_MOCK.examplesByNames.Walk
|
||||||
|
habitsTracker.addHabit(habit)
|
||||||
|
habit.name = "Run"
|
||||||
|
habitsTracker.editHabit(habit)
|
||||||
|
expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([
|
||||||
|
new HabitHistory({
|
||||||
|
habit,
|
||||||
|
progressHistory: [],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not edit habit in habitsHistory when habit is not found", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
const habit = HABIT_MOCK.examplesByNames.Walk
|
||||||
|
habitsTracker.editHabit(habit)
|
||||||
|
expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("updateHabitProgress", () => {
|
||||||
|
it("should update habit progress in habitsHistory (add new habit progress if not yet added)", () => {
|
||||||
|
const habitsTracker = HabitsTracker.default()
|
||||||
|
const habit = HABIT_MOCK.examplesByNames["Clean the house"]
|
||||||
|
habitsTracker.addHabit(habit)
|
||||||
|
habitsTracker.updateHabitProgress(HABIT_PROGRESS_MOCK.exampleByIds[1])
|
||||||
|
expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([
|
||||||
|
new HabitHistory({
|
||||||
|
habit,
|
||||||
|
progressHistory: [HABIT_PROGRESS_MOCK.exampleByIds[1]],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,19 +1,19 @@
|
|||||||
import { AuthenticationUseCase } from "@/domain/use-cases/Authentication"
|
import { AuthenticationUseCase } from "@/domain/use-cases/Authentication"
|
||||||
|
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
||||||
|
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
||||||
|
import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
|
||||||
|
import { HabitStopUseCase } from "@/domain/use-cases/HabitStop"
|
||||||
|
import { AuthenticationPresenter } from "@/presentation/presenters/Authentication"
|
||||||
import { RetrieveHabitsTrackerUseCase } from "../domain/use-cases/RetrieveHabitsTracker"
|
import { RetrieveHabitsTrackerUseCase } from "../domain/use-cases/RetrieveHabitsTracker"
|
||||||
import { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
|
import { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
|
||||||
import { AuthenticationSupabaseRepository } from "./supabase/repositories/Authentication"
|
import { AuthenticationSupabaseRepository } from "./supabase/repositories/Authentication"
|
||||||
import { GetHabitProgressHistorySupabaseRepository } from "./supabase/repositories/GetHabitProgressHistory"
|
import { GetHabitProgressHistorySupabaseRepository } from "./supabase/repositories/GetHabitProgressHistory"
|
||||||
import { GetHabitsByUserIdSupabaseRepository } from "./supabase/repositories/GetHabitsByUserId"
|
import { GetHabitsByUserIdSupabaseRepository } from "./supabase/repositories/GetHabitsByUserId"
|
||||||
import { supabaseClient } from "./supabase/supabase"
|
|
||||||
import { AuthenticationPresenter } from "@/presentation/presenters/Authentication"
|
|
||||||
import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCreate"
|
import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCreate"
|
||||||
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
|
||||||
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
|
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
|
||||||
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
|
||||||
import { HabitProgressCreateSupabaseRepository } from "./supabase/repositories/HabitProgressCreate"
|
import { HabitProgressCreateSupabaseRepository } from "./supabase/repositories/HabitProgressCreate"
|
||||||
import { HabitProgressUpdateSupabaseRepository } from "./supabase/repositories/HabitProgressUpdate"
|
import { HabitProgressUpdateSupabaseRepository } from "./supabase/repositories/HabitProgressUpdate"
|
||||||
import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
|
import { supabaseClient } from "./supabase/supabase"
|
||||||
import { HabitStopUseCase } from "@/domain/use-cases/HabitStop"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repositories
|
* Repositories
|
||||||
|
79
infrastructure/supabase/data-transfer-objects/HabitDTO.ts
Normal file
79
infrastructure/supabase/data-transfer-objects/HabitDTO.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type { Goal } from "@/domain/entities/Goal"
|
||||||
|
import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
|
||||||
|
import type { HabitCreateData, HabitEditData } from "@/domain/entities/Habit"
|
||||||
|
import { Habit } from "@/domain/entities/Habit"
|
||||||
|
import type {
|
||||||
|
SupabaseHabit,
|
||||||
|
SupabaseHabitInsert,
|
||||||
|
SupabaseHabitUpdate,
|
||||||
|
} from "../supabase"
|
||||||
|
|
||||||
|
export const habitSupabaseDTO = {
|
||||||
|
fromSupabaseToDomain: (supabaseHabit: SupabaseHabit): Habit => {
|
||||||
|
let goal: Goal
|
||||||
|
if (
|
||||||
|
supabaseHabit.goal_target != null &&
|
||||||
|
supabaseHabit.goal_target_unit != null
|
||||||
|
) {
|
||||||
|
goal = new GoalNumeric({
|
||||||
|
frequency: supabaseHabit.goal_frequency,
|
||||||
|
target: {
|
||||||
|
value: supabaseHabit.goal_target,
|
||||||
|
unit: supabaseHabit.goal_target_unit,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
goal = new GoalBoolean({
|
||||||
|
frequency: supabaseHabit.goal_frequency,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const habit = new Habit({
|
||||||
|
id: supabaseHabit.id.toString(),
|
||||||
|
name: supabaseHabit.name,
|
||||||
|
color: supabaseHabit.color,
|
||||||
|
icon: supabaseHabit.icon,
|
||||||
|
userId: supabaseHabit.user_id.toString(),
|
||||||
|
startDate: new Date(supabaseHabit.start_date),
|
||||||
|
endDate:
|
||||||
|
supabaseHabit.end_date != null
|
||||||
|
? new Date(supabaseHabit.end_date)
|
||||||
|
: undefined,
|
||||||
|
goal,
|
||||||
|
})
|
||||||
|
return habit
|
||||||
|
},
|
||||||
|
fromDomainCreateDataToSupabaseInsert: (
|
||||||
|
habitCreateData: HabitCreateData,
|
||||||
|
): SupabaseHabitInsert => {
|
||||||
|
return {
|
||||||
|
name: habitCreateData.name,
|
||||||
|
color: habitCreateData.color,
|
||||||
|
icon: habitCreateData.icon,
|
||||||
|
goal_frequency: habitCreateData.goal.frequency,
|
||||||
|
...(habitCreateData.goal.target.type === "numeric"
|
||||||
|
? {
|
||||||
|
goal_target: habitCreateData.goal.target.value,
|
||||||
|
goal_target_unit: habitCreateData.goal.target.unit,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromDomainEditDataToSupabaseUpdate: (
|
||||||
|
habitEditData: HabitEditData,
|
||||||
|
): SupabaseHabitUpdate => {
|
||||||
|
return {
|
||||||
|
name: habitEditData.name,
|
||||||
|
color: habitEditData.color,
|
||||||
|
icon: habitEditData.icon,
|
||||||
|
end_date: habitEditData?.endDate?.toISOString(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const habitsSupabaseDTO = {
|
||||||
|
fromSupabaseToDomain: (supabaseHabits: SupabaseHabit[]): Habit[] => {
|
||||||
|
return supabaseHabits.map((supabaseHabit) => {
|
||||||
|
return habitSupabaseDTO.fromSupabaseToDomain(supabaseHabit)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import type { Goal, GoalProgress } from "@/domain/entities/Goal"
|
||||||
|
import {
|
||||||
|
GoalBooleanProgress,
|
||||||
|
GoalNumericProgress,
|
||||||
|
} from "@/domain/entities/Goal"
|
||||||
|
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||||
|
import type { HabitProgressCreateOptions } from "@/domain/repositories/HabitProgressCreate"
|
||||||
|
import type { HabitProgressUpdateOptions } from "@/domain/repositories/HabitProgressUpdate"
|
||||||
|
import type {
|
||||||
|
SupabaseHabitProgress,
|
||||||
|
SupabaseHabitProgressInsert,
|
||||||
|
SupabaseHabitProgressUpdate,
|
||||||
|
} from "../supabase"
|
||||||
|
|
||||||
|
export const habitProgressSupabaseDTO = {
|
||||||
|
fromSupabaseToDomain: (
|
||||||
|
supabaseHabitProgress: SupabaseHabitProgress,
|
||||||
|
goal: Goal,
|
||||||
|
): HabitProgress => {
|
||||||
|
let goalProgress: GoalProgress | null = null
|
||||||
|
if (goal.isNumeric()) {
|
||||||
|
goalProgress = new GoalNumericProgress({
|
||||||
|
goal,
|
||||||
|
progress: supabaseHabitProgress.goal_progress,
|
||||||
|
})
|
||||||
|
} else if (goal.isBoolean()) {
|
||||||
|
goalProgress = new GoalBooleanProgress({
|
||||||
|
goal,
|
||||||
|
progress: supabaseHabitProgress.goal_progress === 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const habitProgress = new HabitProgress({
|
||||||
|
id: supabaseHabitProgress.id.toString(),
|
||||||
|
habitId: supabaseHabitProgress.habit_id.toString(),
|
||||||
|
goalProgress: goalProgress as GoalProgress,
|
||||||
|
date: new Date(supabaseHabitProgress.date),
|
||||||
|
})
|
||||||
|
return habitProgress
|
||||||
|
},
|
||||||
|
fromDomainDataToSupabaseInsert: (
|
||||||
|
habitProgressData: HabitProgressCreateOptions["habitProgressData"],
|
||||||
|
): SupabaseHabitProgressInsert => {
|
||||||
|
const { goalProgress, date, habitId } = habitProgressData
|
||||||
|
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
||||||
|
if (goalProgress.isNumeric()) {
|
||||||
|
goalProgressValue = goalProgress.progress
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
habit_id: Number.parseInt(habitId, 10),
|
||||||
|
date: date.toISOString(),
|
||||||
|
goal_progress: goalProgressValue,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromDomainDataToSupabaseUpdate: (
|
||||||
|
habitProgressData: HabitProgressUpdateOptions["habitProgressData"],
|
||||||
|
): SupabaseHabitProgressUpdate => {
|
||||||
|
const { goalProgress, date } = habitProgressData
|
||||||
|
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
||||||
|
if (goalProgress.isNumeric()) {
|
||||||
|
goalProgressValue = goalProgress.progress
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
date: date.toISOString(),
|
||||||
|
goal_progress: goalProgressValue,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const habitProgressHistorySupabaseDTO = {
|
||||||
|
fromSupabaseToDomain: (
|
||||||
|
supabaseHabitHistory: SupabaseHabitProgress[],
|
||||||
|
goal: Goal,
|
||||||
|
): HabitProgress[] => {
|
||||||
|
return supabaseHabitHistory.map((item) => {
|
||||||
|
return habitProgressSupabaseDTO.fromSupabaseToDomain(item, goal)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
import type { GoalCreateData } from "@/domain/entities/Goal"
|
||||||
|
import { HABIT_MOCK } from "@/tests/mocks/domain/Habit"
|
||||||
|
import { SUPABASE_HABIT_MOCK } from "@/tests/mocks/supabase/Habit"
|
||||||
|
import { habitSupabaseDTO, habitsSupabaseDTO } from "../HabitDTO"
|
||||||
|
|
||||||
|
describe("infrastructure/supabase/data-transfer-objects/HabitDTO", () => {
|
||||||
|
describe("habitSupabaseDTO.fromSupabaseToDomain", () => {
|
||||||
|
for (const example of SUPABASE_HABIT_MOCK.examples) {
|
||||||
|
it(`should return correct Habit entity - ${example.name}`, () => {
|
||||||
|
expect(habitSupabaseDTO.fromSupabaseToDomain(example)).toEqual(
|
||||||
|
HABIT_MOCK.examplesByNames[
|
||||||
|
example.name as keyof typeof HABIT_MOCK.examplesByNames
|
||||||
|
],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert", () => {
|
||||||
|
for (const example of HABIT_MOCK.examples) {
|
||||||
|
it(`should return correct SupabaseHabitInsert entity - ${example.name}`, () => {
|
||||||
|
let goalData = {} as GoalCreateData
|
||||||
|
if (example.goal.isBoolean()) {
|
||||||
|
goalData = {
|
||||||
|
frequency: example.goal.frequency,
|
||||||
|
target: { type: "boolean" },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (example.goal.isNumeric()) {
|
||||||
|
goalData = {
|
||||||
|
frequency: example.goal.frequency,
|
||||||
|
target: {
|
||||||
|
type: "numeric",
|
||||||
|
value: example.goal.target.value,
|
||||||
|
unit: example.goal.target.unit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const supabaseData =
|
||||||
|
SUPABASE_HABIT_MOCK.examplesByNames[
|
||||||
|
example.name as keyof typeof SUPABASE_HABIT_MOCK.examplesByNames
|
||||||
|
]
|
||||||
|
expect(
|
||||||
|
habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert({
|
||||||
|
userId: example.userId,
|
||||||
|
name: example.name,
|
||||||
|
color: example.color,
|
||||||
|
icon: example.icon,
|
||||||
|
goal: goalData,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
name: supabaseData.name,
|
||||||
|
color: supabaseData.color,
|
||||||
|
icon: supabaseData.icon,
|
||||||
|
goal_frequency: supabaseData.goal_frequency,
|
||||||
|
...(supabaseData.goal_target != null &&
|
||||||
|
supabaseData.goal_target_unit != null
|
||||||
|
? {
|
||||||
|
goal_target: supabaseData.goal_target,
|
||||||
|
goal_target_unit: supabaseData.goal_target_unit,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate", () => {
|
||||||
|
for (const example of HABIT_MOCK.examples) {
|
||||||
|
it(`should return correct SupabaseHabitUpdate entity - ${example.name}`, () => {
|
||||||
|
const supabaseData =
|
||||||
|
SUPABASE_HABIT_MOCK.examplesByNames[
|
||||||
|
example.name as keyof typeof SUPABASE_HABIT_MOCK.examplesByNames
|
||||||
|
]
|
||||||
|
expect(
|
||||||
|
habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate({
|
||||||
|
name: example.name,
|
||||||
|
color: example.color,
|
||||||
|
icon: example.icon,
|
||||||
|
id: example.id,
|
||||||
|
userId: example.userId,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
name: supabaseData.name,
|
||||||
|
color: supabaseData.color,
|
||||||
|
icon: supabaseData.icon,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("habitsSupabaseDTO.fromSupabaseToDomain", () => {
|
||||||
|
it("should return correct Habits entities", () => {
|
||||||
|
expect(
|
||||||
|
habitsSupabaseDTO.fromSupabaseToDomain(SUPABASE_HABIT_MOCK.examples),
|
||||||
|
).toEqual(HABIT_MOCK.examples)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,22 @@
|
|||||||
|
import type { Habit } from "@/domain/entities/Habit"
|
||||||
|
import { HABIT_MOCK } from "@/tests/mocks/domain/Habit"
|
||||||
|
import { HABIT_PROGRESS_MOCK } from "@/tests/mocks/domain/HabitProgress"
|
||||||
|
import { SUPABASE_HABIT_PROGRESS_MOCK } from "@/tests/mocks/supabase/HabitProgress"
|
||||||
|
import { habitProgressSupabaseDTO } from "../HabitProgressDTO"
|
||||||
|
|
||||||
|
describe("infrastructure/supabase/data-transfer-objects/HabitProgressDTO", () => {
|
||||||
|
describe("habitProgressSupabaseDTO.fromSupabaseToDomain", () => {
|
||||||
|
for (const example of SUPABASE_HABIT_PROGRESS_MOCK.examples) {
|
||||||
|
it(`should return correct HabitProgress entity - ${example.id}`, () => {
|
||||||
|
const habit = HABIT_MOCK.examplesByIds[example.habit_id] as Habit
|
||||||
|
expect(
|
||||||
|
habitProgressSupabaseDTO.fromSupabaseToDomain(example, habit.goal),
|
||||||
|
).toEqual(
|
||||||
|
HABIT_PROGRESS_MOCK.exampleByIds[
|
||||||
|
example.id as keyof typeof HABIT_PROGRESS_MOCK.exampleByIds
|
||||||
|
],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
@ -1,8 +1,8 @@
|
|||||||
import type { Session } from "@supabase/supabase-js"
|
import type { Session } from "@supabase/supabase-js"
|
||||||
|
|
||||||
import type { AuthenticationRepository } from "@/domain/repositories/Authentication"
|
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
|
||||||
import { User } from "@/domain/entities/User"
|
import { User } from "@/domain/entities/User"
|
||||||
|
import type { AuthenticationRepository } from "@/domain/repositories/Authentication"
|
||||||
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
|
|
||||||
export class AuthenticationSupabaseRepository
|
export class AuthenticationSupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import type { GetHabitProgressHistoryRepository } from "@/domain/repositories/GetHabitProgressHistory"
|
import type { GetHabitProgressHistoryRepository } from "@/domain/repositories/GetHabitProgressHistory"
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
import { habitProgressHistorySupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
|
||||||
import type { GoalProgress } from "@/domain/entities/Goal"
|
|
||||||
import {
|
|
||||||
GoalBooleanProgress,
|
|
||||||
GoalNumericProgress,
|
|
||||||
} from "@/domain/entities/Goal"
|
|
||||||
|
|
||||||
export class GetHabitProgressHistorySupabaseRepository
|
export class GetHabitProgressHistorySupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
@ -15,37 +10,15 @@ export class GetHabitProgressHistorySupabaseRepository
|
|||||||
options,
|
options,
|
||||||
) => {
|
) => {
|
||||||
const { habit } = options
|
const { habit } = options
|
||||||
const { data, error } = await this.supabaseClient
|
const { data } = await this.supabaseClient
|
||||||
.from("habits_progresses")
|
.from("habits_progresses")
|
||||||
.select("*")
|
.select("*")
|
||||||
.eq("habit_id", habit.id)
|
.eq("habit_id", habit.id)
|
||||||
if (error != null) {
|
.throwOnError()
|
||||||
throw new Error(error.message)
|
const habitProgressHistory = data as NonNullable<typeof data>
|
||||||
}
|
return habitProgressHistorySupabaseDTO.fromSupabaseToDomain(
|
||||||
const habitProgressHistory = data.map((item) => {
|
habitProgressHistory,
|
||||||
let goalProgress: GoalProgress | null = null
|
habit.goal,
|
||||||
if (habit.goal.isNumeric()) {
|
)
|
||||||
goalProgress = new GoalNumericProgress({
|
|
||||||
goal: habit.goal,
|
|
||||||
progress: item.goal_progress,
|
|
||||||
})
|
|
||||||
} else if (habit.goal.isBoolean()) {
|
|
||||||
goalProgress = new GoalBooleanProgress({
|
|
||||||
goal: habit.goal,
|
|
||||||
progress: item.goal_progress === 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (goalProgress == null) {
|
|
||||||
throw new Error("Goal progress is null.")
|
|
||||||
}
|
|
||||||
const habitProgress = new HabitProgress({
|
|
||||||
id: item.id.toString(),
|
|
||||||
habitId: item.habit_id.toString(),
|
|
||||||
goalProgress,
|
|
||||||
date: new Date(item.date),
|
|
||||||
})
|
|
||||||
return habitProgress
|
|
||||||
})
|
|
||||||
return habitProgressHistory
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import type { GetHabitsByUserIdRepository } from "@/domain/repositories/GetHabitsByUserId"
|
import type { GetHabitsByUserIdRepository } from "@/domain/repositories/GetHabitsByUserId"
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
import { Habit } from "@/domain/entities/Habit"
|
import { habitsSupabaseDTO } from "../data-transfer-objects/HabitDTO"
|
||||||
import type { Goal } from "@/domain/entities/Goal"
|
|
||||||
import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
|
|
||||||
|
|
||||||
export class GetHabitsByUserIdSupabaseRepository
|
export class GetHabitsByUserIdSupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
@ -10,39 +8,12 @@ export class GetHabitsByUserIdSupabaseRepository
|
|||||||
{
|
{
|
||||||
public execute: GetHabitsByUserIdRepository["execute"] = async (options) => {
|
public execute: GetHabitsByUserIdRepository["execute"] = async (options) => {
|
||||||
const { userId } = options
|
const { userId } = options
|
||||||
const { data, error } = await this.supabaseClient
|
const { data } = await this.supabaseClient
|
||||||
.from("habits")
|
.from("habits")
|
||||||
.select("*")
|
.select("*")
|
||||||
.eq("user_id", userId)
|
.eq("user_id", userId)
|
||||||
if (error != null) {
|
.throwOnError()
|
||||||
throw new Error(error.message)
|
const habits = data as NonNullable<typeof data>
|
||||||
}
|
return habitsSupabaseDTO.fromSupabaseToDomain(habits)
|
||||||
return data.map((item) => {
|
|
||||||
let goal: Goal
|
|
||||||
if (item.goal_target != null && item.goal_target_unit != null) {
|
|
||||||
goal = new GoalNumeric({
|
|
||||||
frequency: item.goal_frequency,
|
|
||||||
target: {
|
|
||||||
value: item.goal_target,
|
|
||||||
unit: item.goal_target_unit,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
goal = new GoalBoolean({
|
|
||||||
frequency: item.goal_frequency,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const habit = new Habit({
|
|
||||||
id: item.id.toString(),
|
|
||||||
name: item.name,
|
|
||||||
color: item.color,
|
|
||||||
icon: item.icon,
|
|
||||||
userId: item.user_id.toString(),
|
|
||||||
startDate: new Date(item.start_date),
|
|
||||||
endDate: item.end_date != null ? new Date(item.end_date) : undefined,
|
|
||||||
goal,
|
|
||||||
})
|
|
||||||
return habit
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Habit } from "@/domain/entities/Habit"
|
|
||||||
import type { HabitCreateRepository } from "@/domain/repositories/HabitCreate"
|
import type { HabitCreateRepository } from "@/domain/repositories/HabitCreate"
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
import { Goal } from "@/domain/entities/Goal"
|
import { habitSupabaseDTO } from "../data-transfer-objects/HabitDTO"
|
||||||
|
|
||||||
export class HabitCreateSupabaseRepository
|
export class HabitCreateSupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
@ -9,34 +8,15 @@ export class HabitCreateSupabaseRepository
|
|||||||
{
|
{
|
||||||
public execute: HabitCreateRepository["execute"] = async (options) => {
|
public execute: HabitCreateRepository["execute"] = async (options) => {
|
||||||
const { habitCreateData } = options
|
const { habitCreateData } = options
|
||||||
const { data, error } = await this.supabaseClient
|
const { data } = await this.supabaseClient
|
||||||
.from("habits")
|
.from("habits")
|
||||||
.insert({
|
.insert(
|
||||||
name: habitCreateData.name,
|
habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert(habitCreateData),
|
||||||
color: habitCreateData.color,
|
)
|
||||||
icon: habitCreateData.icon,
|
|
||||||
goal_frequency: habitCreateData.goal.frequency,
|
|
||||||
...(habitCreateData.goal.target.type === "numeric"
|
|
||||||
? {
|
|
||||||
goal_target: habitCreateData.goal.target.value,
|
|
||||||
goal_target_unit: habitCreateData.goal.target.unit,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
})
|
|
||||||
.select("*")
|
.select("*")
|
||||||
const insertedHabit = data?.[0]
|
.single()
|
||||||
if (error != null || insertedHabit == null) {
|
.throwOnError()
|
||||||
throw new Error(error?.message ?? "Failed to create habit.")
|
const insertedHabit = data as NonNullable<typeof data>
|
||||||
}
|
return habitSupabaseDTO.fromSupabaseToDomain(insertedHabit)
|
||||||
const habit = new Habit({
|
|
||||||
id: insertedHabit.id.toString(),
|
|
||||||
userId: insertedHabit.user_id.toString(),
|
|
||||||
name: insertedHabit.name,
|
|
||||||
icon: insertedHabit.icon,
|
|
||||||
goal: Goal.create(habitCreateData.goal),
|
|
||||||
color: insertedHabit.color,
|
|
||||||
startDate: new Date(insertedHabit.start_date),
|
|
||||||
})
|
|
||||||
return habit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Habit } from "@/domain/entities/Habit"
|
|
||||||
import type { HabitEditRepository } from "@/domain/repositories/HabitEdit"
|
import type { HabitEditRepository } from "@/domain/repositories/HabitEdit"
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
import { Goal } from "@/domain/entities/Goal"
|
import { habitSupabaseDTO } from "../data-transfer-objects/HabitDTO"
|
||||||
|
|
||||||
export class HabitEditSupabaseRepository
|
export class HabitEditSupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
@ -9,46 +8,16 @@ export class HabitEditSupabaseRepository
|
|||||||
{
|
{
|
||||||
public execute: HabitEditRepository["execute"] = async (options) => {
|
public execute: HabitEditRepository["execute"] = async (options) => {
|
||||||
const { habitEditData } = options
|
const { habitEditData } = options
|
||||||
const { data, error } = await this.supabaseClient
|
const { data } = await this.supabaseClient
|
||||||
.from("habits")
|
.from("habits")
|
||||||
.update({
|
.update(
|
||||||
name: habitEditData.name,
|
habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate(habitEditData),
|
||||||
color: habitEditData.color,
|
)
|
||||||
icon: habitEditData.icon,
|
|
||||||
end_date: habitEditData?.endDate?.toISOString(),
|
|
||||||
})
|
|
||||||
.eq("id", habitEditData.id)
|
.eq("id", habitEditData.id)
|
||||||
.select("*")
|
.select("*")
|
||||||
const updatedHabit = data?.[0]
|
.single()
|
||||||
if (error != null || updatedHabit == null) {
|
.throwOnError()
|
||||||
throw new Error(error?.message ?? "Failed to edit habit.")
|
const updatedHabit = data as NonNullable<typeof data>
|
||||||
}
|
return habitSupabaseDTO.fromSupabaseToDomain(updatedHabit)
|
||||||
const habit = new Habit({
|
|
||||||
id: updatedHabit.id.toString(),
|
|
||||||
userId: updatedHabit.user_id.toString(),
|
|
||||||
name: updatedHabit.name,
|
|
||||||
icon: updatedHabit.icon,
|
|
||||||
goal: Goal.create({
|
|
||||||
frequency: updatedHabit.goal_frequency,
|
|
||||||
target:
|
|
||||||
updatedHabit.goal_target != null &&
|
|
||||||
updatedHabit.goal_target_unit != null
|
|
||||||
? {
|
|
||||||
type: "numeric",
|
|
||||||
value: updatedHabit.goal_target,
|
|
||||||
unit: updatedHabit.goal_target_unit,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: "boolean",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
color: updatedHabit.color,
|
|
||||||
startDate: new Date(updatedHabit.start_date),
|
|
||||||
endDate:
|
|
||||||
updatedHabit.end_date != null
|
|
||||||
? new Date(updatedHabit.end_date)
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
return habit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { HabitProgressCreateRepository } from "@/domain/repositories/HabitProgressCreate"
|
import type { HabitProgressCreateRepository } from "@/domain/repositories/HabitProgressCreate"
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
import { habitProgressSupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
|
||||||
|
|
||||||
export class HabitProgressCreateSupabaseRepository
|
export class HabitProgressCreateSupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
@ -10,29 +10,20 @@ export class HabitProgressCreateSupabaseRepository
|
|||||||
options,
|
options,
|
||||||
) => {
|
) => {
|
||||||
const { habitProgressData } = options
|
const { habitProgressData } = options
|
||||||
const { goalProgress, date, habitId } = habitProgressData
|
const { data } = await this.supabaseClient
|
||||||
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
|
||||||
if (goalProgress.isNumeric()) {
|
|
||||||
goalProgressValue = goalProgress.progress
|
|
||||||
}
|
|
||||||
const { data, error } = await this.supabaseClient
|
|
||||||
.from("habits_progresses")
|
.from("habits_progresses")
|
||||||
.insert({
|
.insert(
|
||||||
habit_id: Number(habitId),
|
habitProgressSupabaseDTO.fromDomainDataToSupabaseInsert(
|
||||||
date: date.toISOString(),
|
habitProgressData,
|
||||||
goal_progress: goalProgressValue,
|
),
|
||||||
})
|
)
|
||||||
.select("*")
|
.select("*")
|
||||||
const insertedProgress = data?.[0]
|
.single()
|
||||||
if (error != null || insertedProgress == null) {
|
.throwOnError()
|
||||||
throw new Error(error?.message ?? "Failed to create habit progress.")
|
const insertedProgress = data as NonNullable<typeof data>
|
||||||
}
|
return habitProgressSupabaseDTO.fromSupabaseToDomain(
|
||||||
const habitProgress = new HabitProgress({
|
insertedProgress,
|
||||||
id: insertedProgress.id.toString(),
|
habitProgressData.goalProgress.goal,
|
||||||
habitId: insertedProgress.habit_id.toString(),
|
)
|
||||||
date: new Date(insertedProgress.date),
|
|
||||||
goalProgress,
|
|
||||||
})
|
|
||||||
return habitProgress
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { HabitProgressUpdateRepository } from "@/domain/repositories/HabitProgressUpdate"
|
import type { HabitProgressUpdateRepository } from "@/domain/repositories/HabitProgressUpdate"
|
||||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
import { habitProgressSupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
|
||||||
|
|
||||||
export class HabitProgressUpdateSupabaseRepository
|
export class HabitProgressUpdateSupabaseRepository
|
||||||
extends SupabaseRepository
|
extends SupabaseRepository
|
||||||
@ -10,29 +10,21 @@ export class HabitProgressUpdateSupabaseRepository
|
|||||||
options,
|
options,
|
||||||
) => {
|
) => {
|
||||||
const { habitProgressData } = options
|
const { habitProgressData } = options
|
||||||
const { id, goalProgress, date } = habitProgressData
|
const { data } = await this.supabaseClient
|
||||||
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
|
||||||
if (goalProgress.isNumeric()) {
|
|
||||||
goalProgressValue = goalProgress.progress
|
|
||||||
}
|
|
||||||
const { data, error } = await this.supabaseClient
|
|
||||||
.from("habits_progresses")
|
.from("habits_progresses")
|
||||||
.update({
|
.update(
|
||||||
date: date.toISOString(),
|
habitProgressSupabaseDTO.fromDomainDataToSupabaseUpdate(
|
||||||
goal_progress: goalProgressValue,
|
habitProgressData,
|
||||||
})
|
),
|
||||||
.eq("id", id)
|
)
|
||||||
|
.eq("id", habitProgressData.id)
|
||||||
.select("*")
|
.select("*")
|
||||||
const insertedProgress = data?.[0]
|
.single()
|
||||||
if (error != null || insertedProgress == null) {
|
.throwOnError()
|
||||||
throw new Error(error?.message ?? "Failed to update habit progress.")
|
const insertedProgress = data as NonNullable<typeof data>
|
||||||
}
|
return habitProgressSupabaseDTO.fromSupabaseToDomain(
|
||||||
const habitProgress = new HabitProgress({
|
insertedProgress,
|
||||||
id: insertedProgress.id.toString(),
|
habitProgressData.goalProgress.goal,
|
||||||
habitId: insertedProgress.habit_id.toString(),
|
)
|
||||||
date: new Date(insertedProgress.date),
|
|
||||||
goalProgress,
|
|
||||||
})
|
|
||||||
return habitProgress
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,19 @@ 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 SupabaseUser = SupabaseUserType
|
||||||
|
|
||||||
export type SupabaseHabit = Database["public"]["Tables"]["habits"]["Row"]
|
export type SupabaseHabit = Database["public"]["Tables"]["habits"]["Row"]
|
||||||
|
export type SupabaseHabitInsert =
|
||||||
|
Database["public"]["Tables"]["habits"]["Insert"]
|
||||||
|
export type SupabaseHabitUpdate =
|
||||||
|
Database["public"]["Tables"]["habits"]["Update"]
|
||||||
|
|
||||||
export type SupabaseHabitProgress =
|
export type SupabaseHabitProgress =
|
||||||
Database["public"]["Tables"]["habits_progresses"]["Row"]
|
Database["public"]["Tables"]["habits_progresses"]["Row"]
|
||||||
|
export type SupabaseHabitProgressInsert =
|
||||||
|
Database["public"]["Tables"]["habits_progresses"]["Insert"]
|
||||||
|
export type SupabaseHabitProgressUpdate =
|
||||||
|
Database["public"]["Tables"]["habits_progresses"]["Update"]
|
||||||
|
|
||||||
const SUPABASE_URL =
|
const SUPABASE_URL =
|
||||||
process.env["EXPO_PUBLIC_SUPABASE_URL"] ??
|
process.env["EXPO_PUBLIC_SUPABASE_URL"] ??
|
||||||
|
@ -10,7 +10,13 @@
|
|||||||
"coverageReporters": ["text", "text-summary", "cobertura"],
|
"coverageReporters": ["text", "text-summary", "cobertura"],
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"<rootDir>/**/*.{ts,tsx}",
|
"<rootDir>/**/*.{ts,tsx}",
|
||||||
|
"!<rootDir>/tests/**/*",
|
||||||
|
"!<rootDir>/domain/repositories/**/*",
|
||||||
|
"!<rootDir>/infrastructure/instances.ts",
|
||||||
|
"!<rootDir>/infrastructure/supabase/supabase-types.ts",
|
||||||
|
"!<rootDir>/infrastructure/supabase/supabase.ts",
|
||||||
"!<rootDir>/presentation/react-native/ui/ExternalLink.tsx",
|
"!<rootDir>/presentation/react-native/ui/ExternalLink.tsx",
|
||||||
|
"!<rootDir>/presentation/react/contexts/**/*",
|
||||||
"!<rootDir>/.expo",
|
"!<rootDir>/.expo",
|
||||||
"!<rootDir>/app/+html.tsx",
|
"!<rootDir>/app/+html.tsx",
|
||||||
"!<rootDir>/app/**/_layout.tsx",
|
"!<rootDir>/app/**/_layout.tsx",
|
||||||
|
@ -41,11 +41,8 @@ export abstract class Presenter<State> {
|
|||||||
|
|
||||||
public unsubscribe(listener: Listener<State>): void {
|
public unsubscribe(listener: Listener<State>): void {
|
||||||
const listenerIndex = this._listeners.indexOf(listener)
|
const listenerIndex = this._listeners.indexOf(listener)
|
||||||
const listenerFound = listenerIndex !== -1
|
|
||||||
if (listenerFound) {
|
|
||||||
this._listeners.splice(listenerIndex, 1)
|
this._listeners.splice(listenerIndex, 1)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private notifyListeners(): void {
|
private notifyListeners(): void {
|
||||||
for (const listener of this._listeners) {
|
for (const listener of this._listeners) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import type { IconName } from "@fortawesome/fontawesome-svg-core"
|
||||||
import { fas } from "@fortawesome/free-solid-svg-icons"
|
import { fas } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||||
import { memo, useCallback, useEffect, useState, useTransition } from "react"
|
import { memo, useCallback, useEffect, useState, useTransition } from "react"
|
||||||
import { Modal, ScrollView, View } from "react-native"
|
import { Modal, ScrollView, View } from "react-native"
|
||||||
import { Button, List, Text, TextInput } from "react-native-paper"
|
import { Button, List, Text, TextInput } from "react-native-paper"
|
||||||
import type { IconName } from "@fortawesome/fontawesome-svg-core"
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
|
||||||
|
|
||||||
import { IconsList } from "./IconsList"
|
import { IconsList } from "./IconsList"
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
|
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||||
import { useRouter } from "expo-router"
|
import { useRouter } from "expo-router"
|
||||||
|
import type LottieView from "lottie-react-native"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { View } from "react-native"
|
import { View } from "react-native"
|
||||||
import { Checkbox, List, Text } from "react-native-paper"
|
import { Checkbox, List, Text } from "react-native-paper"
|
||||||
import type LottieView from "lottie-react-native"
|
|
||||||
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
|
||||||
|
|
||||||
import type { GoalBoolean } from "@/domain/entities/Goal"
|
import type { GoalBoolean } from "@/domain/entities/Goal"
|
||||||
import { GoalBooleanProgress } from "@/domain/entities/Goal"
|
import { GoalBooleanProgress } from "@/domain/entities/Goal"
|
||||||
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
|
||||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||||
|
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||||
|
|
||||||
export interface HabitCardProps {
|
export interface HabitCardProps {
|
||||||
habitHistory: HabitHistory
|
habitHistory: HabitHistory
|
||||||
|
@ -5,8 +5,8 @@ import { Divider, List } from "react-native-paper"
|
|||||||
|
|
||||||
import type { GoalFrequency } from "@/domain/entities/Goal"
|
import type { GoalFrequency } from "@/domain/entities/Goal"
|
||||||
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
||||||
import confettiJSON from "../../../assets/confetti.json"
|
|
||||||
import { capitalize } from "@/utils/strings"
|
import { capitalize } from "@/utils/strings"
|
||||||
|
import confettiJSON from "../../../assets/confetti.json"
|
||||||
import { HabitCard } from "./HabitCard"
|
import { HabitCard } from "./HabitCard"
|
||||||
|
|
||||||
export interface HabitsListProps {
|
export interface HabitsListProps {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { createContext, useContext, useEffect } from "react"
|
import { createContext, useContext, useEffect } from "react"
|
||||||
|
|
||||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
import { authenticationPresenter } from "@/infrastructure/instances"
|
||||||
import type {
|
import type {
|
||||||
AuthenticationPresenter,
|
AuthenticationPresenter,
|
||||||
AuthenticationPresenterState,
|
AuthenticationPresenterState,
|
||||||
} from "@/presentation/presenters/Authentication"
|
} from "@/presentation/presenters/Authentication"
|
||||||
import { authenticationPresenter } from "@/infrastructure/instances"
|
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||||
|
|
||||||
export interface AuthenticationContextValue
|
export interface AuthenticationContextValue
|
||||||
extends AuthenticationPresenterState {
|
extends AuthenticationPresenterState {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { createContext, useContext, useEffect } from "react"
|
import { createContext, useContext, useEffect } from "react"
|
||||||
|
|
||||||
|
import { habitsTrackerPresenter } from "@/infrastructure/instances"
|
||||||
import type {
|
import type {
|
||||||
HabitsTrackerPresenter,
|
HabitsTrackerPresenter,
|
||||||
HabitsTrackerPresenterState,
|
HabitsTrackerPresenterState,
|
||||||
} from "@/presentation/presenters/HabitsTracker"
|
} from "@/presentation/presenters/HabitsTracker"
|
||||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||||
import { habitsTrackerPresenter } from "@/infrastructure/instances"
|
|
||||||
import { useAuthentication } from "./Authentication"
|
import { useAuthentication } from "./Authentication"
|
||||||
|
|
||||||
export interface HabitsTrackerContextValue extends HabitsTrackerPresenterState {
|
export interface HabitsTrackerContextValue extends HabitsTrackerPresenterState {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { act, renderHook } from "@testing-library/react-native"
|
import { act, renderHook } from "@testing-library/react-native"
|
||||||
|
|
||||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
|
||||||
import { Presenter } from "@/presentation/presenters/_Presenter"
|
import { Presenter } from "@/presentation/presenters/_Presenter"
|
||||||
|
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||||
|
|
||||||
interface MockCountPresenterState {
|
interface MockCountPresenterState {
|
||||||
count: number
|
count: number
|
||||||
|
115
tests/mocks/domain/Habit.ts
Normal file
115
tests/mocks/domain/Habit.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
|
||||||
|
import type { HabitData } from "@/domain/entities/Habit"
|
||||||
|
import { Habit } from "@/domain/entities/Habit"
|
||||||
|
import { USER_MOCK } from "./User"
|
||||||
|
import { ONE_DAY_MILLISECONDS } from "@/utils/dates"
|
||||||
|
|
||||||
|
interface HabitMockCreateOptions extends Omit<HabitData, "startDate"> {
|
||||||
|
startDate?: Date
|
||||||
|
}
|
||||||
|
const habitMockCreate = (options: HabitMockCreateOptions): Habit => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
icon,
|
||||||
|
goal,
|
||||||
|
startDate = new Date(),
|
||||||
|
endDate,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
return new Habit({
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
icon,
|
||||||
|
goal,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const examplesByNames = {
|
||||||
|
"Wake up at 07h00": habitMockCreate({
|
||||||
|
id: "1",
|
||||||
|
userId: USER_MOCK.example.id,
|
||||||
|
name: "Wake up at 07h00",
|
||||||
|
color: "#006CFF",
|
||||||
|
icon: "bed",
|
||||||
|
goal: new GoalBoolean({
|
||||||
|
frequency: "daily",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"Learn English": habitMockCreate({
|
||||||
|
id: "2",
|
||||||
|
userId: USER_MOCK.example.id,
|
||||||
|
name: "Learn English",
|
||||||
|
color: "#EB4034",
|
||||||
|
icon: "language",
|
||||||
|
goal: new GoalNumeric({
|
||||||
|
frequency: "daily",
|
||||||
|
target: {
|
||||||
|
value: 30,
|
||||||
|
unit: "minutes",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Walk: habitMockCreate({
|
||||||
|
id: "3",
|
||||||
|
userId: USER_MOCK.example.id,
|
||||||
|
name: "Walk",
|
||||||
|
color: "#228B22",
|
||||||
|
icon: "person-walking",
|
||||||
|
goal: new GoalNumeric({
|
||||||
|
frequency: "daily",
|
||||||
|
target: {
|
||||||
|
value: 5000,
|
||||||
|
unit: "steps",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"Clean the house": habitMockCreate({
|
||||||
|
id: "4",
|
||||||
|
userId: USER_MOCK.example.id,
|
||||||
|
name: "Clean the house",
|
||||||
|
color: "#808080",
|
||||||
|
icon: "broom",
|
||||||
|
goal: new GoalBoolean({
|
||||||
|
frequency: "weekly",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"Solve Programming Challenges": habitMockCreate({
|
||||||
|
id: "5",
|
||||||
|
userId: USER_MOCK.example.id,
|
||||||
|
name: "Solve Programming Challenges",
|
||||||
|
color: "#DE3163",
|
||||||
|
icon: "code",
|
||||||
|
goal: new GoalNumeric({
|
||||||
|
frequency: "monthly",
|
||||||
|
target: {
|
||||||
|
value: 5,
|
||||||
|
unit: "challenges",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
endDate: new Date(Date.now() + ONE_DAY_MILLISECONDS),
|
||||||
|
}),
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const examplesByIds = {
|
||||||
|
[examplesByNames["Wake up at 07h00"].id]: examplesByNames["Wake up at 07h00"],
|
||||||
|
[examplesByNames["Learn English"].id]: examplesByNames["Learn English"],
|
||||||
|
[examplesByNames.Walk.id]: examplesByNames.Walk,
|
||||||
|
[examplesByNames["Clean the house"].id]: examplesByNames["Clean the house"],
|
||||||
|
[examplesByNames["Solve Programming Challenges"].id]:
|
||||||
|
examplesByNames["Solve Programming Challenges"],
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const HABIT_MOCK = {
|
||||||
|
create: habitMockCreate,
|
||||||
|
example: examplesByNames["Wake up at 07h00"],
|
||||||
|
examplesByNames,
|
||||||
|
examplesByIds,
|
||||||
|
examples: Object.values(examplesByNames),
|
||||||
|
}
|
51
tests/mocks/domain/HabitProgress.ts
Normal file
51
tests/mocks/domain/HabitProgress.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import type { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
|
||||||
|
import {
|
||||||
|
GoalBooleanProgress,
|
||||||
|
GoalNumericProgress,
|
||||||
|
} from "@/domain/entities/Goal"
|
||||||
|
import type { HabitProgressData } from "@/domain/entities/HabitProgress"
|
||||||
|
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||||
|
import { HABIT_MOCK } from "./Habit"
|
||||||
|
|
||||||
|
interface HabitProgressMockCreateOptions
|
||||||
|
extends Omit<HabitProgressData, "date"> {
|
||||||
|
date?: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
const habitProgressMockCreate = (
|
||||||
|
options: HabitProgressMockCreateOptions,
|
||||||
|
): HabitProgress => {
|
||||||
|
const { id, habitId, goalProgress, date = new Date() } = options
|
||||||
|
|
||||||
|
return new HabitProgress({
|
||||||
|
date,
|
||||||
|
goalProgress,
|
||||||
|
habitId,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const exampleByIds = {
|
||||||
|
1: habitProgressMockCreate({
|
||||||
|
id: "1",
|
||||||
|
habitId: HABIT_MOCK.examplesByNames["Clean the house"].id,
|
||||||
|
goalProgress: new GoalBooleanProgress({
|
||||||
|
goal: HABIT_MOCK.examplesByNames["Clean the house"].goal as GoalBoolean,
|
||||||
|
progress: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
2: habitProgressMockCreate({
|
||||||
|
id: "2",
|
||||||
|
habitId: HABIT_MOCK.examplesByNames.Walk.id,
|
||||||
|
goalProgress: new GoalNumericProgress({
|
||||||
|
goal: HABIT_MOCK.examplesByNames.Walk.goal as GoalNumeric,
|
||||||
|
progress: 4_733,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const HABIT_PROGRESS_MOCK = {
|
||||||
|
create: habitProgressMockCreate,
|
||||||
|
exampleByIds,
|
||||||
|
examples: Object.values(exampleByIds),
|
||||||
|
}
|
30
tests/mocks/domain/User.ts
Normal file
30
tests/mocks/domain/User.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { UserData } from "@/domain/entities/User"
|
||||||
|
import { User } from "@/domain/entities/User"
|
||||||
|
|
||||||
|
const USER_MOCK_ID = "ab054ee9-fbb4-473e-942b-bbf4415f4bef"
|
||||||
|
const USER_MOCK_EMAIL = "test@test.com"
|
||||||
|
const USER_MOCK_DISPLAY_NAME = "Test"
|
||||||
|
|
||||||
|
interface UserMockCreateOptions {
|
||||||
|
id?: UserData["id"]
|
||||||
|
email?: UserData["email"]
|
||||||
|
displayName?: UserData["displayName"]
|
||||||
|
}
|
||||||
|
const userMockCreate = (options: UserMockCreateOptions = {}): User => {
|
||||||
|
const {
|
||||||
|
id = USER_MOCK_ID,
|
||||||
|
email = USER_MOCK_EMAIL,
|
||||||
|
displayName = USER_MOCK_DISPLAY_NAME,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
return new User({
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
displayName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const USER_MOCK = {
|
||||||
|
create: userMockCreate,
|
||||||
|
example: userMockCreate(),
|
||||||
|
}
|
79
tests/mocks/supabase/Habit.ts
Normal file
79
tests/mocks/supabase/Habit.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type { SupabaseHabit } from "@/infrastructure/supabase/supabase"
|
||||||
|
import { HABIT_MOCK } from "../domain/Habit"
|
||||||
|
import { SUPABASE_USER_MOCK } from "./User"
|
||||||
|
|
||||||
|
interface SupabaseHabitMockCreateOptions {
|
||||||
|
id: SupabaseHabit["id"]
|
||||||
|
userId: SupabaseHabit["user_id"]
|
||||||
|
name: SupabaseHabit["name"]
|
||||||
|
color: SupabaseHabit["color"]
|
||||||
|
icon: SupabaseHabit["icon"]
|
||||||
|
startDate?: Date
|
||||||
|
endDate: Date | null
|
||||||
|
goalFrequency: SupabaseHabit["goal_frequency"]
|
||||||
|
goalTarget: SupabaseHabit["goal_target"] | null
|
||||||
|
goalTargetUnit: SupabaseHabit["goal_target_unit"] | null
|
||||||
|
}
|
||||||
|
const supabaseHabitMockCreate = (
|
||||||
|
options: SupabaseHabitMockCreateOptions,
|
||||||
|
): SupabaseHabit => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
icon,
|
||||||
|
startDate = new Date(),
|
||||||
|
endDate,
|
||||||
|
goalFrequency,
|
||||||
|
goalTarget,
|
||||||
|
goalTargetUnit,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
user_id: userId,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
icon,
|
||||||
|
start_date: startDate.toISOString(),
|
||||||
|
end_date: endDate?.toISOString() ?? null,
|
||||||
|
goal_frequency: goalFrequency,
|
||||||
|
goal_target: goalTarget,
|
||||||
|
goal_target_unit: goalTargetUnit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const examplesByNames = Object.fromEntries(
|
||||||
|
Object.entries(HABIT_MOCK.examplesByNames).map(([name, habit]) => {
|
||||||
|
const goalTarget = habit.goal.isNumeric() ? habit.goal.target.value : null
|
||||||
|
const goalTargetUnit = habit.goal.isNumeric()
|
||||||
|
? habit.goal.target.unit
|
||||||
|
: null
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
supabaseHabitMockCreate({
|
||||||
|
id: Number.parseInt(habit.id, 10),
|
||||||
|
userId: SUPABASE_USER_MOCK.example.id,
|
||||||
|
name: habit.name,
|
||||||
|
color: habit.color,
|
||||||
|
icon: habit.icon,
|
||||||
|
startDate: habit.startDate,
|
||||||
|
endDate: habit.endDate ?? null,
|
||||||
|
goalFrequency: habit.goal.frequency,
|
||||||
|
goalTarget,
|
||||||
|
goalTargetUnit,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
) as {
|
||||||
|
[key in keyof (typeof HABIT_MOCK)["examplesByNames"]]: SupabaseHabit
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SUPABASE_HABIT_MOCK = {
|
||||||
|
create: supabaseHabitMockCreate,
|
||||||
|
example:
|
||||||
|
examplesByNames[HABIT_MOCK.example.name as keyof typeof examplesByNames],
|
||||||
|
examples: Object.values(examplesByNames),
|
||||||
|
examplesByNames,
|
||||||
|
}
|
49
tests/mocks/supabase/HabitProgress.ts
Normal file
49
tests/mocks/supabase/HabitProgress.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import type { SupabaseHabitProgress } from "@/infrastructure/supabase/supabase"
|
||||||
|
import { HABIT_PROGRESS_MOCK } from "../domain/HabitProgress"
|
||||||
|
|
||||||
|
interface SupabaseHabitProgressMockCreateOptions {
|
||||||
|
id: SupabaseHabitProgress["id"]
|
||||||
|
habitId: SupabaseHabitProgress["habit_id"]
|
||||||
|
date?: Date
|
||||||
|
goalProgress: SupabaseHabitProgress["goal_progress"]
|
||||||
|
}
|
||||||
|
const supabaseHabitProgressMockCreate = (
|
||||||
|
options: SupabaseHabitProgressMockCreateOptions,
|
||||||
|
): SupabaseHabitProgress => {
|
||||||
|
const { id, habitId, date = new Date(), goalProgress } = options
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
habit_id: habitId,
|
||||||
|
date: date.toISOString(),
|
||||||
|
goal_progress: goalProgress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exampleByIds = Object.fromEntries(
|
||||||
|
Object.entries(HABIT_PROGRESS_MOCK.exampleByIds).map(
|
||||||
|
([id, habitProgress]) => {
|
||||||
|
return [
|
||||||
|
id,
|
||||||
|
supabaseHabitProgressMockCreate({
|
||||||
|
id: Number.parseInt(habitProgress.id, 10),
|
||||||
|
habitId: Number.parseInt(habitProgress.habitId, 10),
|
||||||
|
date: new Date(habitProgress.date),
|
||||||
|
goalProgress: habitProgress.goalProgress.isNumeric()
|
||||||
|
? habitProgress.goalProgress.progress
|
||||||
|
: habitProgress.goalProgress.isCompleted()
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) as {
|
||||||
|
[key in keyof (typeof HABIT_PROGRESS_MOCK)["exampleByIds"]]: SupabaseHabitProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SUPABASE_HABIT_PROGRESS_MOCK = {
|
||||||
|
create: supabaseHabitProgressMockCreate,
|
||||||
|
exampleByIds,
|
||||||
|
examples: Object.values(exampleByIds),
|
||||||
|
}
|
63
tests/mocks/supabase/User.ts
Normal file
63
tests/mocks/supabase/User.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import type { SupabaseUser } from "@/infrastructure/supabase/supabase"
|
||||||
|
import { USER_MOCK } from "../domain/User"
|
||||||
|
|
||||||
|
interface SupabaseUserMockCreateOptions {
|
||||||
|
id?: SupabaseUser["id"]
|
||||||
|
email?: SupabaseUser["email"]
|
||||||
|
displayName?: SupabaseUser["user_metadata"]["display_name"]
|
||||||
|
date?: Date
|
||||||
|
}
|
||||||
|
const supabaseUserMockCreate = (
|
||||||
|
options: SupabaseUserMockCreateOptions = {},
|
||||||
|
): SupabaseUser => {
|
||||||
|
const {
|
||||||
|
id = USER_MOCK.example.id,
|
||||||
|
email = USER_MOCK.example.email,
|
||||||
|
displayName = USER_MOCK.example.displayName,
|
||||||
|
date = new Date(),
|
||||||
|
} = options
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
app_metadata: { provider: "email", providers: ["email"] },
|
||||||
|
user_metadata: { display_name: displayName },
|
||||||
|
aud: "authenticated",
|
||||||
|
email,
|
||||||
|
confirmation_sent_at: undefined,
|
||||||
|
recovery_sent_at: undefined,
|
||||||
|
email_change_sent_at: undefined,
|
||||||
|
new_email: "",
|
||||||
|
new_phone: "",
|
||||||
|
invited_at: undefined,
|
||||||
|
action_link: "",
|
||||||
|
created_at: date.toISOString(),
|
||||||
|
confirmed_at: undefined,
|
||||||
|
email_confirmed_at: date.toISOString(),
|
||||||
|
phone_confirmed_at: undefined,
|
||||||
|
last_sign_in_at: undefined,
|
||||||
|
role: "authenticated",
|
||||||
|
updated_at: date.toISOString(),
|
||||||
|
identities: [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
user_id: id,
|
||||||
|
identity_data: {
|
||||||
|
sub: id,
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
provider: "email",
|
||||||
|
identity_id: id,
|
||||||
|
last_sign_in_at: date.toISOString(),
|
||||||
|
created_at: date.toISOString(),
|
||||||
|
updated_at: date.toISOString(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
is_anonymous: false,
|
||||||
|
factors: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SUPABASE_USER_MOCK = {
|
||||||
|
create: supabaseUserMockCreate,
|
||||||
|
example: supabaseUserMockCreate(),
|
||||||
|
}
|
Reference in New Issue
Block a user