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 type { Habit } from "./Habit"
|
||||
import type { HabitProgress } from "./HabitProgress"
|
||||
import type { GoalProgress } from "./Goal"
|
||||
import { GoalBooleanProgress, GoalNumericProgress } from "./Goal"
|
||||
import type { Habit } from "./Habit"
|
||||
import type { HabitProgress } from "./HabitProgress"
|
||||
|
||||
export interface HabitHistoryJSON {
|
||||
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 { 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 { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
|
||||
import { AuthenticationSupabaseRepository } from "./supabase/repositories/Authentication"
|
||||
import { GetHabitProgressHistorySupabaseRepository } from "./supabase/repositories/GetHabitProgressHistory"
|
||||
import { GetHabitsByUserIdSupabaseRepository } from "./supabase/repositories/GetHabitsByUserId"
|
||||
import { supabaseClient } from "./supabase/supabase"
|
||||
import { AuthenticationPresenter } from "@/presentation/presenters/Authentication"
|
||||
import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCreate"
|
||||
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
||||
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
|
||||
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
||||
import { HabitProgressCreateSupabaseRepository } from "./supabase/repositories/HabitProgressCreate"
|
||||
import { HabitProgressUpdateSupabaseRepository } from "./supabase/repositories/HabitProgressUpdate"
|
||||
import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
|
||||
import { HabitStopUseCase } from "@/domain/use-cases/HabitStop"
|
||||
import { supabaseClient } from "./supabase/supabase"
|
||||
|
||||
/**
|
||||
* 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 { AuthenticationRepository } from "@/domain/repositories/Authentication"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { User } from "@/domain/entities/User"
|
||||
import type { AuthenticationRepository } from "@/domain/repositories/Authentication"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
|
||||
export class AuthenticationSupabaseRepository
|
||||
extends SupabaseRepository
|
||||
|
@ -1,11 +1,6 @@
|
||||
import type { GetHabitProgressHistoryRepository } from "@/domain/repositories/GetHabitProgressHistory"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||
import type { GoalProgress } from "@/domain/entities/Goal"
|
||||
import {
|
||||
GoalBooleanProgress,
|
||||
GoalNumericProgress,
|
||||
} from "@/domain/entities/Goal"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
import { habitProgressHistorySupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
|
||||
|
||||
export class GetHabitProgressHistorySupabaseRepository
|
||||
extends SupabaseRepository
|
||||
@ -15,37 +10,15 @@ export class GetHabitProgressHistorySupabaseRepository
|
||||
options,
|
||||
) => {
|
||||
const { habit } = options
|
||||
const { data, error } = await this.supabaseClient
|
||||
const { data } = await this.supabaseClient
|
||||
.from("habits_progresses")
|
||||
.select("*")
|
||||
.eq("habit_id", habit.id)
|
||||
if (error != null) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
const habitProgressHistory = data.map((item) => {
|
||||
let goalProgress: GoalProgress | null = null
|
||||
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
|
||||
.throwOnError()
|
||||
const habitProgressHistory = data as NonNullable<typeof data>
|
||||
return habitProgressHistorySupabaseDTO.fromSupabaseToDomain(
|
||||
habitProgressHistory,
|
||||
habit.goal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import type { GetHabitsByUserIdRepository } from "@/domain/repositories/GetHabitsByUserId"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { Habit } from "@/domain/entities/Habit"
|
||||
import type { Goal } from "@/domain/entities/Goal"
|
||||
import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
import { habitsSupabaseDTO } from "../data-transfer-objects/HabitDTO"
|
||||
|
||||
export class GetHabitsByUserIdSupabaseRepository
|
||||
extends SupabaseRepository
|
||||
@ -10,39 +8,12 @@ export class GetHabitsByUserIdSupabaseRepository
|
||||
{
|
||||
public execute: GetHabitsByUserIdRepository["execute"] = async (options) => {
|
||||
const { userId } = options
|
||||
const { data, error } = await this.supabaseClient
|
||||
const { data } = await this.supabaseClient
|
||||
.from("habits")
|
||||
.select("*")
|
||||
.eq("user_id", userId)
|
||||
if (error != null) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
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
|
||||
})
|
||||
.throwOnError()
|
||||
const habits = data as NonNullable<typeof data>
|
||||
return habitsSupabaseDTO.fromSupabaseToDomain(habits)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Habit } from "@/domain/entities/Habit"
|
||||
import type { HabitCreateRepository } from "@/domain/repositories/HabitCreate"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { Goal } from "@/domain/entities/Goal"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
import { habitSupabaseDTO } from "../data-transfer-objects/HabitDTO"
|
||||
|
||||
export class HabitCreateSupabaseRepository
|
||||
extends SupabaseRepository
|
||||
@ -9,34 +8,15 @@ export class HabitCreateSupabaseRepository
|
||||
{
|
||||
public execute: HabitCreateRepository["execute"] = async (options) => {
|
||||
const { habitCreateData } = options
|
||||
const { data, error } = await this.supabaseClient
|
||||
const { data } = await this.supabaseClient
|
||||
.from("habits")
|
||||
.insert({
|
||||
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,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
.insert(
|
||||
habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert(habitCreateData),
|
||||
)
|
||||
.select("*")
|
||||
const insertedHabit = data?.[0]
|
||||
if (error != null || insertedHabit == null) {
|
||||
throw new Error(error?.message ?? "Failed to create habit.")
|
||||
}
|
||||
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
|
||||
.single()
|
||||
.throwOnError()
|
||||
const insertedHabit = data as NonNullable<typeof data>
|
||||
return habitSupabaseDTO.fromSupabaseToDomain(insertedHabit)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Habit } from "@/domain/entities/Habit"
|
||||
import type { HabitEditRepository } from "@/domain/repositories/HabitEdit"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { Goal } from "@/domain/entities/Goal"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
import { habitSupabaseDTO } from "../data-transfer-objects/HabitDTO"
|
||||
|
||||
export class HabitEditSupabaseRepository
|
||||
extends SupabaseRepository
|
||||
@ -9,46 +8,16 @@ export class HabitEditSupabaseRepository
|
||||
{
|
||||
public execute: HabitEditRepository["execute"] = async (options) => {
|
||||
const { habitEditData } = options
|
||||
const { data, error } = await this.supabaseClient
|
||||
const { data } = await this.supabaseClient
|
||||
.from("habits")
|
||||
.update({
|
||||
name: habitEditData.name,
|
||||
color: habitEditData.color,
|
||||
icon: habitEditData.icon,
|
||||
end_date: habitEditData?.endDate?.toISOString(),
|
||||
})
|
||||
.update(
|
||||
habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate(habitEditData),
|
||||
)
|
||||
.eq("id", habitEditData.id)
|
||||
.select("*")
|
||||
const updatedHabit = data?.[0]
|
||||
if (error != null || updatedHabit == null) {
|
||||
throw new Error(error?.message ?? "Failed to edit habit.")
|
||||
}
|
||||
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
|
||||
.single()
|
||||
.throwOnError()
|
||||
const updatedHabit = data as NonNullable<typeof data>
|
||||
return habitSupabaseDTO.fromSupabaseToDomain(updatedHabit)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { HabitProgressCreateRepository } from "@/domain/repositories/HabitProgressCreate"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
import { habitProgressSupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
|
||||
|
||||
export class HabitProgressCreateSupabaseRepository
|
||||
extends SupabaseRepository
|
||||
@ -10,29 +10,20 @@ export class HabitProgressCreateSupabaseRepository
|
||||
options,
|
||||
) => {
|
||||
const { habitProgressData } = options
|
||||
const { goalProgress, date, habitId } = habitProgressData
|
||||
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
||||
if (goalProgress.isNumeric()) {
|
||||
goalProgressValue = goalProgress.progress
|
||||
}
|
||||
const { data, error } = await this.supabaseClient
|
||||
const { data } = await this.supabaseClient
|
||||
.from("habits_progresses")
|
||||
.insert({
|
||||
habit_id: Number(habitId),
|
||||
date: date.toISOString(),
|
||||
goal_progress: goalProgressValue,
|
||||
})
|
||||
.insert(
|
||||
habitProgressSupabaseDTO.fromDomainDataToSupabaseInsert(
|
||||
habitProgressData,
|
||||
),
|
||||
)
|
||||
.select("*")
|
||||
const insertedProgress = data?.[0]
|
||||
if (error != null || insertedProgress == null) {
|
||||
throw new Error(error?.message ?? "Failed to create habit progress.")
|
||||
}
|
||||
const habitProgress = new HabitProgress({
|
||||
id: insertedProgress.id.toString(),
|
||||
habitId: insertedProgress.habit_id.toString(),
|
||||
date: new Date(insertedProgress.date),
|
||||
goalProgress,
|
||||
})
|
||||
return habitProgress
|
||||
.single()
|
||||
.throwOnError()
|
||||
const insertedProgress = data as NonNullable<typeof data>
|
||||
return habitProgressSupabaseDTO.fromSupabaseToDomain(
|
||||
insertedProgress,
|
||||
habitProgressData.goalProgress.goal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { HabitProgressUpdateRepository } from "@/domain/repositories/HabitProgressUpdate"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||
import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
|
||||
import { habitProgressSupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
|
||||
|
||||
export class HabitProgressUpdateSupabaseRepository
|
||||
extends SupabaseRepository
|
||||
@ -10,29 +10,21 @@ export class HabitProgressUpdateSupabaseRepository
|
||||
options,
|
||||
) => {
|
||||
const { habitProgressData } = options
|
||||
const { id, goalProgress, date } = habitProgressData
|
||||
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
||||
if (goalProgress.isNumeric()) {
|
||||
goalProgressValue = goalProgress.progress
|
||||
}
|
||||
const { data, error } = await this.supabaseClient
|
||||
const { data } = await this.supabaseClient
|
||||
.from("habits_progresses")
|
||||
.update({
|
||||
date: date.toISOString(),
|
||||
goal_progress: goalProgressValue,
|
||||
})
|
||||
.eq("id", id)
|
||||
.update(
|
||||
habitProgressSupabaseDTO.fromDomainDataToSupabaseUpdate(
|
||||
habitProgressData,
|
||||
),
|
||||
)
|
||||
.eq("id", habitProgressData.id)
|
||||
.select("*")
|
||||
const insertedProgress = data?.[0]
|
||||
if (error != null || insertedProgress == null) {
|
||||
throw new Error(error?.message ?? "Failed to update habit progress.")
|
||||
}
|
||||
const habitProgress = new HabitProgress({
|
||||
id: insertedProgress.id.toString(),
|
||||
habitId: insertedProgress.habit_id.toString(),
|
||||
date: new Date(insertedProgress.date),
|
||||
goalProgress,
|
||||
})
|
||||
return habitProgress
|
||||
.single()
|
||||
.throwOnError()
|
||||
const insertedProgress = data as NonNullable<typeof data>
|
||||
return habitProgressSupabaseDTO.fromSupabaseToDomain(
|
||||
insertedProgress,
|
||||
habitProgressData.goalProgress.goal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,19 @@ import AsyncStorage from "@react-native-async-storage/async-storage"
|
||||
import type { Database } from "./supabase-types"
|
||||
|
||||
export type SupabaseUser = SupabaseUserType
|
||||
|
||||
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 =
|
||||
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 =
|
||||
process.env["EXPO_PUBLIC_SUPABASE_URL"] ??
|
||||
|
@ -10,7 +10,13 @@
|
||||
"coverageReporters": ["text", "text-summary", "cobertura"],
|
||||
"collectCoverageFrom": [
|
||||
"<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/contexts/**/*",
|
||||
"!<rootDir>/.expo",
|
||||
"!<rootDir>/app/+html.tsx",
|
||||
"!<rootDir>/app/**/_layout.tsx",
|
||||
|
@ -41,10 +41,7 @@ export abstract class Presenter<State> {
|
||||
|
||||
public unsubscribe(listener: Listener<State>): void {
|
||||
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 {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { IconName } from "@fortawesome/fontawesome-svg-core"
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import { memo, useCallback, useEffect, useState, useTransition } from "react"
|
||||
import { Modal, ScrollView, View } from "react-native"
|
||||
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"
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import { useRouter } from "expo-router"
|
||||
import type LottieView from "lottie-react-native"
|
||||
import { useState } from "react"
|
||||
import { View } from "react-native"
|
||||
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 { GoalBooleanProgress } from "@/domain/entities/Goal"
|
||||
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||
|
||||
export interface HabitCardProps {
|
||||
habitHistory: HabitHistory
|
||||
|
@ -5,8 +5,8 @@ import { Divider, List } from "react-native-paper"
|
||||
|
||||
import type { GoalFrequency } from "@/domain/entities/Goal"
|
||||
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
||||
import confettiJSON from "../../../assets/confetti.json"
|
||||
import { capitalize } from "@/utils/strings"
|
||||
import confettiJSON from "../../../assets/confetti.json"
|
||||
import { HabitCard } from "./HabitCard"
|
||||
|
||||
export interface HabitsListProps {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { createContext, useContext, useEffect } from "react"
|
||||
|
||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||
import { authenticationPresenter } from "@/infrastructure/instances"
|
||||
import type {
|
||||
AuthenticationPresenter,
|
||||
AuthenticationPresenterState,
|
||||
} from "@/presentation/presenters/Authentication"
|
||||
import { authenticationPresenter } from "@/infrastructure/instances"
|
||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||
|
||||
export interface AuthenticationContextValue
|
||||
extends AuthenticationPresenterState {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { createContext, useContext, useEffect } from "react"
|
||||
|
||||
import { habitsTrackerPresenter } from "@/infrastructure/instances"
|
||||
import type {
|
||||
HabitsTrackerPresenter,
|
||||
HabitsTrackerPresenterState,
|
||||
} from "@/presentation/presenters/HabitsTracker"
|
||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||
import { habitsTrackerPresenter } from "@/infrastructure/instances"
|
||||
import { useAuthentication } from "./Authentication"
|
||||
|
||||
export interface HabitsTrackerContextValue extends HabitsTrackerPresenterState {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { act, renderHook } from "@testing-library/react-native"
|
||||
|
||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||
import { Presenter } from "@/presentation/presenters/_Presenter"
|
||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||
|
||||
interface MockCountPresenterState {
|
||||
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