refactor: project structure
@ -15,10 +15,9 @@ Un tracker d'habitudes pour performer au boulot et dans la vie de tous les jours
|
||||
|
||||
### Documentation
|
||||
|
||||
- [Sujet du projet](./docs/SUJET.md)
|
||||
- [Cahier des charges](./docs/CAHIER-DES-CHARGES.md)
|
||||
- [Sujet](./docs/SUJET.md) + [Cahier des charges](./docs/CAHIER-DES-CHARGES.md)
|
||||
- [Modèle Logique des Données (MLD)](./docs/MLD.md)
|
||||
- [Clean Architecture](./docs/ARCHITECTURE.md)
|
||||
- [Architecture](./docs/ARCHITECTURE.md)
|
||||
- [Conventions développement informatique](./docs/CONVENTIONS.md)
|
||||
|
||||
#### Principaux Outils Informatiques Utilisés
|
||||
@ -26,8 +25,10 @@ Un tracker d'habitudes pour performer au boulot et dans la vie de tous les jours
|
||||
- [React Native](https://reactnative.dev/) + [Expo](https://expo.io/): Framework pour le développement d'applications mobiles.
|
||||
- [TypeScript](https://www.typescriptlang.org/): Langage de programmation.
|
||||
- [React Native Paper](https://callstack.github.io/react-native-paper/): Bibliothèque de composants pour React Native.
|
||||
<!-- - [WatermelonDB](https://nozbe.github.io/WatermelonDB/): Base de données locale, pour permettre une utilisation hors-ligne de l'application. -->
|
||||
- [Supabase](https://supabase.io/): Backend, serveur d'API pour le stockage des données.
|
||||
<!--
|
||||
- [WatermelonDB](https://nozbe.github.io/WatermelonDB/): Base de données locale, pour permettre une utilisation hors-ligne de l'application.
|
||||
-->
|
||||
|
||||
## Développement du projet en local
|
||||
|
||||
|
8
app.json
@ -4,11 +4,11 @@
|
||||
"slug": "p61-project",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"icon": "./presentation/assets/images/icon.png",
|
||||
"scheme": "p61-project",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"splash": {
|
||||
"image": "./assets/images/splashscreen.jpg",
|
||||
"image": "./presentation/assets/images/splashscreen.jpg",
|
||||
"resizeMode": "cover",
|
||||
"backgroundColor": "#74b6cb"
|
||||
},
|
||||
@ -18,14 +18,14 @@
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"foregroundImage": "./presentation/assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
"favicon": "./presentation/assets/images/favicon.png"
|
||||
},
|
||||
"plugins": ["expo-router"],
|
||||
"experiments": {
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { StyleSheet, Text, View } from "react-native"
|
||||
import { SafeAreaView } from "react-native-safe-area-context"
|
||||
|
||||
import { useHabitsTracker } from "@/contexts/HabitsTracker"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
|
||||
const HabitsPage: React.FC = () => {
|
||||
const { habitsTrackerPresenterState } = useHabitsTracker()
|
||||
const { habitsTracker } = habitsTrackerPresenterState
|
||||
const { habitProgressHistories } = habitsTracker
|
||||
const { habitsTracker } = useHabitsTracker()
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
{habitProgressHistories.map((progressHistory) => {
|
||||
{habitsTracker.habitsHistory.map((progressHistory) => {
|
||||
const { habit } = progressHistory
|
||||
|
||||
return (
|
||||
|
@ -8,10 +8,10 @@ import {
|
||||
} from "react-native-paper"
|
||||
import { StatusBar } from "expo-status-bar"
|
||||
|
||||
import CanterburyFont from "../assets/fonts/Canterbury.ttf"
|
||||
import GeoramFont from "../assets/fonts/Georama-Black.ttf"
|
||||
import SpaceMonoFont from "../assets/fonts/SpaceMono-Regular.ttf"
|
||||
import { HabitsTrackerProvider } from "@/contexts/HabitsTracker"
|
||||
import CanterburyFont from "../presentation/assets/fonts/Canterbury.ttf"
|
||||
import GeoramFont from "../presentation/assets/fonts/Georama-Black.ttf"
|
||||
import SpaceMonoFont from "../presentation/assets/fonts/SpaceMono-Regular.ttf"
|
||||
import { HabitsTrackerProvider } from "@/presentation/react/contexts/HabitsTracker"
|
||||
|
||||
export { ErrorBoundary } from "expo-router"
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
import type { Habit } from "./Habit"
|
||||
import type { HabitProgress } from "./HabitProgress"
|
||||
|
||||
export interface HabitProgressHistoryOptions {
|
||||
habit: Habit
|
||||
habitProgresses: HabitProgress[]
|
||||
}
|
||||
|
||||
export class HabitProgressHistory implements HabitProgressHistoryOptions {
|
||||
public habit: Habit
|
||||
public habitProgresses: HabitProgress[]
|
||||
|
||||
public constructor(options: HabitProgressHistoryOptions) {
|
||||
const { habit, habitProgresses } = options
|
||||
this.habit = habit
|
||||
this.habitProgresses = habitProgresses
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import type { HabitProgressHistory } from "./HabitProgressHistory"
|
||||
|
||||
export interface HabitsTrackerOptions {
|
||||
habitProgressHistories: HabitProgressHistory[]
|
||||
}
|
||||
|
||||
export class HabitsTracker implements HabitsTrackerOptions {
|
||||
public habitProgressHistories: HabitProgressHistory[]
|
||||
|
||||
public constructor(options: HabitsTrackerOptions) {
|
||||
const { habitProgressHistories } = options
|
||||
this.habitProgressHistories = habitProgressHistories
|
||||
}
|
||||
|
||||
public static default(): HabitsTracker {
|
||||
return new HabitsTracker({ habitProgressHistories: [] })
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import type { Habit } from "../entities/Habit"
|
||||
import type { HabitProgress } from "../entities/HabitProgress"
|
||||
|
||||
export interface GetHabitProgressesOptions {
|
||||
habit: Habit
|
||||
}
|
||||
|
||||
export interface GetHabitProgressesRepository {
|
||||
execute: (options: GetHabitProgressesOptions) => Promise<HabitProgress[]>
|
||||
}
|
@ -51,4 +51,4 @@ Vous serez évalués sur les aspects suivants :
|
||||
|
||||
## Exemple d’interfaces
|
||||
|
||||
![UI Example](../assets/images/ui-example.png)
|
||||
![UI Example](../presentation/assets/images/ui-example.png)
|
||||
|
@ -22,15 +22,17 @@ Une pipeline CI ([`.gitlab-ci.yml`](.gitlab-ci.yml)) est en place pour vérifier
|
||||
|
||||
Le projet suit la convention [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) reposant sur 2 branches principales:
|
||||
|
||||
- `main` (ou `master`): Contient le code de la dernière version stable et déployé en production.
|
||||
- `main`: Contient le code de la dernière version stable et déployé en production.
|
||||
- `develop`: Contient le code en cours de développement. Les nouvelles fonctionnalités et les correctifs de bugs sont fusionnés ici.
|
||||
|
||||
Chaque nouvelle fonctionnalité ou correctif de bug est développé dans une branche dédiée à partir de `develop`, nommée `feat/<nom-de-la-fonctionnalité>` ou `fix/<nom-du-bug>`. Une fois le développement terminé, une merge request est créée pour demander une revue de code, et une fois validée, la branche est fusionnée dans `develop`, puis supprimée.
|
||||
|
||||
## Convention des commits
|
||||
|
||||
Les commits respectent la convention [Conventional Commits](https://www.conventionalcommits.org/) et [Semantic Versioning](https://semver.org/) pour la gestion des versions et des releases en fonction des commits.
|
||||
|
||||
Les commits doivent être **atomiques** c'est à dire qu'il respecte 3 règles:
|
||||
|
||||
- Ne concerne qu'un seul sujet (une fonctionnalité, une correction de bug, etc.)
|
||||
- Doit avoir un message clair et concis
|
||||
- Ne doit pas rendre de dépôt "incohérent" (bloque la CI du projet)
|
||||
- Ne concerne qu'un seul sujet (une fonctionnalité, une correction de bug, etc.).
|
||||
- Doit avoir un message clair et concis.
|
||||
- Ne doit pas rendre de dépôt "incohérent" (bloque la CI du projet).
|
||||
|
18
domain/entities/HabitHistory.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { Habit } from "./Habit"
|
||||
import type { HabitProgress } from "./HabitProgress"
|
||||
|
||||
export interface HabitHistoryOptions {
|
||||
habit: Habit
|
||||
progressHistory: HabitProgress[]
|
||||
}
|
||||
|
||||
export class HabitHistory implements HabitHistoryOptions {
|
||||
public habit: Habit
|
||||
public progressHistory: HabitProgress[]
|
||||
|
||||
public constructor(options: HabitHistoryOptions) {
|
||||
const { habit, progressHistory } = options
|
||||
this.habit = habit
|
||||
this.progressHistory = progressHistory
|
||||
}
|
||||
}
|
18
domain/entities/HabitsTracker.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { HabitHistory } from "./HabitHistory"
|
||||
|
||||
export interface HabitsTrackerOptions {
|
||||
habitsHistory: HabitHistory[]
|
||||
}
|
||||
|
||||
export class HabitsTracker implements HabitsTrackerOptions {
|
||||
public habitsHistory: HabitHistory[]
|
||||
|
||||
public constructor(options: HabitsTrackerOptions) {
|
||||
const { habitsHistory } = options
|
||||
this.habitsHistory = habitsHistory
|
||||
}
|
||||
|
||||
public static default(): HabitsTracker {
|
||||
return new HabitsTracker({ habitsHistory: [] })
|
||||
}
|
||||
}
|
10
domain/repositories/GetHabitProgressHistory.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { Habit } from "../entities/Habit"
|
||||
import type { HabitProgress } from "../entities/HabitProgress"
|
||||
|
||||
export interface GetHabitProgressHistoryOptions {
|
||||
habit: Habit
|
||||
}
|
||||
|
||||
export interface GetHabitProgressHistoryRepository {
|
||||
execute: (options: GetHabitProgressHistoryOptions) => Promise<HabitProgress[]>
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { HabitProgressHistory } from "../entities/HabitProgressHistory"
|
||||
import { HabitHistory } from "../entities/HabitHistory"
|
||||
import { HabitsTracker } from "../entities/HabitsTracker"
|
||||
import type { User } from "../entities/User"
|
||||
import type { GetHabitProgressesRepository } from "../repositories/GetHabitProgresses"
|
||||
import type { GetHabitProgressHistoryRepository } from "../repositories/GetHabitProgressHistory"
|
||||
import type { GetHabitsByUserIdRepository } from "../repositories/GetHabitsByUserId"
|
||||
|
||||
export interface RetrieveHabitsTrackerUseCaseDependencyOptions {
|
||||
getHabitsByUserIdRepository: GetHabitsByUserIdRepository
|
||||
getHabitProgressesRepository: GetHabitProgressesRepository
|
||||
getHabitProgressHistoryRepository: GetHabitProgressHistoryRepository
|
||||
}
|
||||
|
||||
export interface RetrieveHabitsTrackerUseCaseOptions {
|
||||
@ -17,11 +17,12 @@ export class RetrieveHabitsTrackerUseCase
|
||||
implements RetrieveHabitsTrackerUseCaseDependencyOptions
|
||||
{
|
||||
public getHabitsByUserIdRepository: GetHabitsByUserIdRepository
|
||||
public getHabitProgressesRepository: GetHabitProgressesRepository
|
||||
public getHabitProgressHistoryRepository: GetHabitProgressHistoryRepository
|
||||
|
||||
public constructor(options: RetrieveHabitsTrackerUseCaseDependencyOptions) {
|
||||
this.getHabitsByUserIdRepository = options.getHabitsByUserIdRepository
|
||||
this.getHabitProgressesRepository = options.getHabitProgressesRepository
|
||||
this.getHabitProgressHistoryRepository =
|
||||
options.getHabitProgressHistoryRepository
|
||||
}
|
||||
|
||||
public async execute(
|
||||
@ -31,15 +32,16 @@ export class RetrieveHabitsTrackerUseCase
|
||||
const habits = await this.getHabitsByUserIdRepository.execute({ userId })
|
||||
const habitProgressHistories = await Promise.all(
|
||||
habits.map(async (habit) => {
|
||||
const habitProgresses = await this.getHabitProgressesRepository.execute(
|
||||
{
|
||||
const progressHistory =
|
||||
await this.getHabitProgressHistoryRepository.execute({
|
||||
habit,
|
||||
},
|
||||
)
|
||||
return new HabitProgressHistory({ habit, habitProgresses })
|
||||
})
|
||||
return new HabitHistory({ habit, progressHistory })
|
||||
}),
|
||||
)
|
||||
const habitsTracker = new HabitsTracker({ habitProgressHistories })
|
||||
const habitsTracker = new HabitsTracker({
|
||||
habitsHistory: habitProgressHistories,
|
||||
})
|
||||
return habitsTracker
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
import { act, renderHook, waitFor } from "@testing-library/react-native"
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage"
|
||||
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage"
|
||||
|
||||
describe("hooks/useLocalStorage", () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await AsyncStorage.clear()
|
||||
})
|
||||
|
||||
it("should get the default value", () => {
|
||||
const key = "key"
|
||||
const givenDefaultValue = { key: "value" }
|
||||
const { result } = renderHook(() => {
|
||||
return useLocalStorage(key, givenDefaultValue)
|
||||
})
|
||||
const [actualValue] = result.current
|
||||
expect(actualValue).toEqual(givenDefaultValue)
|
||||
})
|
||||
|
||||
it("should get the value from the storage", async () => {
|
||||
const key = "key"
|
||||
const givenDefaultValue = { someValue: "value" }
|
||||
const storedValue = { someValue: "value" }
|
||||
const { result } = renderHook(() => {
|
||||
return useLocalStorage(key, givenDefaultValue)
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(AsyncStorage.getItem).toHaveBeenCalledWith(key)
|
||||
})
|
||||
const [actualValue] = result.current
|
||||
expect(actualValue).toEqual(storedValue)
|
||||
})
|
||||
|
||||
it("should set the value to the storage", async () => {
|
||||
const key = "key"
|
||||
const givenDefaultValue = { someValue: "value" }
|
||||
const storedValue = { someValue: "value" }
|
||||
const { result } = renderHook(() => {
|
||||
return useLocalStorage(key, givenDefaultValue)
|
||||
})
|
||||
const [, setValue] = result.current
|
||||
await act(() => {
|
||||
setValue(storedValue)
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||
key,
|
||||
JSON.stringify(storedValue),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("should get default value if storage value is not valid JSON", async () => {
|
||||
console.error = jest.fn()
|
||||
const key = "key"
|
||||
const givenDefaultValue = { someValue: "value" }
|
||||
const storedValue = "{not valid JSON"
|
||||
await AsyncStorage.setItem(key, storedValue)
|
||||
const { result } = renderHook(() => {
|
||||
return useLocalStorage(key, givenDefaultValue)
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(AsyncStorage.getItem).toHaveBeenCalledWith(key)
|
||||
})
|
||||
const [actualValue] = result.current
|
||||
expect(actualValue).toEqual(givenDefaultValue)
|
||||
})
|
||||
|
||||
it("should catch the error when setting the value to the storage", async () => {
|
||||
console.error = jest.fn()
|
||||
const key = "key"
|
||||
const givenDefaultValue = { someValue: "value" }
|
||||
const storedValue = { someValue: "value" }
|
||||
const error = new Error("error")
|
||||
;(AsyncStorage.setItem as jest.Mock).mockRejectedValue(error)
|
||||
const { result } = renderHook(() => {
|
||||
return useLocalStorage(key, givenDefaultValue)
|
||||
})
|
||||
const [, setValue] = result.current
|
||||
await act(() => {
|
||||
setValue(storedValue)
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||
key,
|
||||
JSON.stringify(storedValue),
|
||||
)
|
||||
})
|
||||
expect(console.error).toHaveBeenCalledWith(error)
|
||||
})
|
||||
})
|
@ -1,43 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage"
|
||||
|
||||
type UseLocalStorageResult<T> = [T, React.Dispatch<React.SetStateAction<T>>]
|
||||
|
||||
export const useLocalStorage = <T extends unknown>(
|
||||
key: string,
|
||||
defaultValue: T,
|
||||
): UseLocalStorageResult<T> => {
|
||||
const hasSaved = useRef(false)
|
||||
const [value, setValue] = useState<T>(defaultValue)
|
||||
|
||||
const getFromLocalStorage = useCallback(async (): Promise<T> => {
|
||||
const value = await AsyncStorage.getItem(key)
|
||||
hasSaved.current = true
|
||||
if (value == null) {
|
||||
return defaultValue
|
||||
}
|
||||
return JSON.parse(value) as T
|
||||
}, [key, defaultValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasSaved.current) {
|
||||
return
|
||||
}
|
||||
AsyncStorage.setItem(key, JSON.stringify(value)).catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
}, [key, value])
|
||||
|
||||
useEffect(() => {
|
||||
getFromLocalStorage()
|
||||
.then((value) => {
|
||||
setValue(value)
|
||||
})
|
||||
.catch((error) => {
|
||||
setValue(defaultValue)
|
||||
console.error(error)
|
||||
})
|
||||
}, [defaultValue, getFromLocalStorage])
|
||||
|
||||
return [value, setValue]
|
||||
}
|
@ -1,17 +1,14 @@
|
||||
// export const taskRepository = new TaskLocalStorageRepository()
|
||||
// export const taskService = new TaskService(taskRepository)
|
||||
// export const taskPresenter = new TaskPresenter(taskService)
|
||||
|
||||
import { RetrieveHabitsTrackerUseCase } from "../domain/use-cases/RetrieveHabitsTracker"
|
||||
import { HabitsTrackerPresenter } from "./presenters/HabitsTrackerPresenter"
|
||||
import { GetHabitProgressesSupabaseRepository } from "./repositories/supabase/lib/GetHabitProgresses"
|
||||
import { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
|
||||
import { GetHabitProgressHistorySupabaseRepository } from "./repositories/supabase/lib/GetHabitProgressHistory"
|
||||
import { GetHabitsByUserIdSupabaseRepository } from "./repositories/supabase/lib/GetHabitsByUserId"
|
||||
import { supabaseClient } from "./repositories/supabase/supabase"
|
||||
|
||||
/**
|
||||
* Repositories
|
||||
*/
|
||||
const getHabitProgressesRepository = new GetHabitProgressesSupabaseRepository({
|
||||
const getHabitProgressesRepository =
|
||||
new GetHabitProgressHistorySupabaseRepository({
|
||||
supabaseClient,
|
||||
})
|
||||
const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
|
||||
@ -22,7 +19,7 @@ const getHabitsByUserIdRepository = new GetHabitsByUserIdSupabaseRepository({
|
||||
* Use Cases
|
||||
*/
|
||||
const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
|
||||
getHabitProgressesRepository,
|
||||
getHabitProgressHistoryRepository: getHabitProgressesRepository,
|
||||
getHabitsByUserIdRepository,
|
||||
})
|
||||
|
@ -1,17 +1,17 @@
|
||||
import type { GetHabitProgressesRepository } from "@/data/domain/repositories/GetHabitProgresses"
|
||||
import type { GetHabitProgressHistoryRepository } from "@/domain/repositories/GetHabitProgressHistory"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { HabitProgress } from "@/data/domain/entities/HabitProgress"
|
||||
import type { GoalProgress } from "@/data/domain/entities/Goal"
|
||||
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||
import type { GoalProgress } from "@/domain/entities/Goal"
|
||||
import {
|
||||
GoalBooleanProgress,
|
||||
GoalNumericProgress,
|
||||
} from "@/data/domain/entities/Goal"
|
||||
} from "@/domain/entities/Goal"
|
||||
|
||||
export class GetHabitProgressesSupabaseRepository
|
||||
export class GetHabitProgressHistorySupabaseRepository
|
||||
extends SupabaseRepository
|
||||
implements GetHabitProgressesRepository
|
||||
implements GetHabitProgressHistoryRepository
|
||||
{
|
||||
execute: GetHabitProgressesRepository["execute"] = async (options) => {
|
||||
execute: GetHabitProgressHistoryRepository["execute"] = async (options) => {
|
||||
const { habit } = options
|
||||
const { data, error } = await this.supabaseClient
|
||||
.from("habits_progresses")
|
||||
@ -20,7 +20,7 @@ export class GetHabitProgressesSupabaseRepository
|
||||
if (error != null) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
const habitProgresses = data.map((item) => {
|
||||
const habitProgressHistory = data.map((item) => {
|
||||
let goalProgress: GoalProgress | null = null
|
||||
if (habit.goal.isNumeric()) {
|
||||
goalProgress = new GoalNumericProgress({
|
||||
@ -44,6 +44,6 @@ export class GetHabitProgressesSupabaseRepository
|
||||
})
|
||||
return habitProgress
|
||||
})
|
||||
return habitProgresses
|
||||
return habitProgressHistory
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import type { GetHabitsByUserIdRepository } from "@/data/domain/repositories/GetHabitsByUserId"
|
||||
import type { GetHabitsByUserIdRepository } from "@/domain/repositories/GetHabitsByUserId"
|
||||
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||
import { Habit } from "@/data/domain/entities/Habit"
|
||||
import type { Goal } from "@/data/domain/entities/Goal"
|
||||
import { GoalBoolean, GoalNumeric } from "@/data/domain/entities/Goal"
|
||||
import { Habit } from "@/domain/entities/Habit"
|
||||
import type { Goal } from "@/domain/entities/Goal"
|
||||
import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
|
||||
|
||||
export class GetHabitsByUserIdSupabaseRepository
|
||||
extends SupabaseRepository
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"preset": "jest-expo",
|
||||
"roots": ["./"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/tests/jest.setup.ts"],
|
||||
"setupFilesAfterEnv": ["@testing-library/react-native/extend-expect"],
|
||||
"fakeTimers": {
|
||||
"enableGlobally": true
|
||||
},
|
||||
@ -10,7 +10,7 @@
|
||||
"coverageReporters": ["text", "text-summary", "cobertura"],
|
||||
"collectCoverageFrom": [
|
||||
"<rootDir>/**/*.{ts,tsx}",
|
||||
"!<rootDir>/components/ExternalLink.tsx",
|
||||
"!<rootDir>/presentation/react/components/ExternalLink.tsx",
|
||||
"!<rootDir>/.expo",
|
||||
"!<rootDir>/app/+html.tsx",
|
||||
"!<rootDir>/app/**/_layout.tsx",
|
||||
|
59
package-lock.json
generated
@ -10,7 +10,6 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "14.0.0",
|
||||
"@react-native-async-storage/async-storage": "1.21.0",
|
||||
"@react-navigation/native": "6.1.16",
|
||||
"@supabase/supabase-js": "2.39.8",
|
||||
"expo": "50.0.13",
|
||||
@ -34,7 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.0",
|
||||
"@commitlint/cli": "19.1.0",
|
||||
"@commitlint/cli": "19.2.0",
|
||||
"@commitlint/config-conventional": "19.1.0",
|
||||
"@testing-library/react-native": "12.4.3",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
@ -51,7 +50,7 @@
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-react": "7.34.0",
|
||||
"eslint-plugin-react": "7.34.1",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"eslint-plugin-react-native": "4.1.0",
|
||||
"eslint-plugin-unicorn": "51.0.1",
|
||||
@ -2122,15 +2121,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@commitlint/cli": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.1.0.tgz",
|
||||
"integrity": "sha512-SYGm8HGbVzrlSYeB6oo6pG1Ec6bOMJcDsXgNGa4vgZQsPj6nJkcbTWlIRmtmIk0tHi0d5sCljGuQ+g/0NCPv7w==",
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.2.0.tgz",
|
||||
"integrity": "sha512-8XnQDMyQR+1/ldbmIyhonvnDS2enEw48Wompo/967fsEvy9Vj5/JbDutzmSBKxANWDVeEbR9QQm0yHpw6ArrFw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@commitlint/format": "^19.0.3",
|
||||
"@commitlint/lint": "^19.1.0",
|
||||
"@commitlint/load": "^19.1.0",
|
||||
"@commitlint/read": "^19.0.3",
|
||||
"@commitlint/load": "^19.2.0",
|
||||
"@commitlint/read": "^19.2.0",
|
||||
"@commitlint/types": "^19.0.3",
|
||||
"execa": "^8.0.1",
|
||||
"yargs": "^17.0.0"
|
||||
@ -5292,17 +5291,6 @@
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-async-storage/async-storage": {
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz",
|
||||
"integrity": "sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==",
|
||||
"dependencies": {
|
||||
"merge-options": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": "^0.0.0-0 || >=0.60 <1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-community/cli": {
|
||||
"version": "12.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.6.tgz",
|
||||
@ -11525,9 +11513,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.34.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.0.tgz",
|
||||
"integrity": "sha512-MeVXdReleBTdkz/bvcQMSnCXGi+c9kvy51IpinjnJgutl3YTHWsDdke7Z1ufZpGfDG8xduBDKyjtB9JH1eBKIQ==",
|
||||
"version": "7.34.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz",
|
||||
"integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.7",
|
||||
@ -13997,11 +13985,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
|
||||
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
@ -17761,17 +17749,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-options": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
|
||||
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
|
||||
"dependencies": {
|
||||
"is-plain-obj": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@ -21261,14 +21238,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-keys/node_modules/is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
|
@ -19,7 +19,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "14.0.0",
|
||||
"@react-native-async-storage/async-storage": "1.21.0",
|
||||
"@react-navigation/native": "6.1.16",
|
||||
"@supabase/supabase-js": "2.39.8",
|
||||
"expo": "50.0.13",
|
||||
@ -43,7 +42,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.0",
|
||||
"@commitlint/cli": "19.1.0",
|
||||
"@commitlint/cli": "19.2.0",
|
||||
"@commitlint/config-conventional": "19.1.0",
|
||||
"@testing-library/react-native": "12.4.3",
|
||||
"@total-typescript/ts-reset": "0.5.1",
|
||||
@ -60,7 +59,7 @@
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-react": "7.34.0",
|
||||
"eslint-plugin-react": "7.34.1",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"eslint-plugin-react-native": "4.1.0",
|
||||
"eslint-plugin-unicorn": "51.0.1",
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
@ -1,9 +1,9 @@
|
||||
import { HabitsTracker } from "@/data/domain/entities/HabitsTracker"
|
||||
import { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
||||
import { Presenter } from "./_Presenter"
|
||||
import type {
|
||||
RetrieveHabitsTrackerUseCase,
|
||||
RetrieveHabitsTrackerUseCaseOptions,
|
||||
} from "@/data/domain/use-cases/RetrieveHabitsTracker"
|
||||
} from "@/domain/use-cases/RetrieveHabitsTracker"
|
||||
|
||||
export interface HabitsTrackerPresenterState {
|
||||
habitsTracker: HabitsTracker
|
@ -1,6 +1,6 @@
|
||||
import renderer from "react-test-renderer"
|
||||
|
||||
import { ButtonCustom } from "@/components/ButtonCustom"
|
||||
import { ButtonCustom } from "@/presentation/react/components/ButtonCustom"
|
||||
|
||||
describe("<ButtonCustom />", () => {
|
||||
it("renders correctly", () => {
|
@ -1,6 +1,6 @@
|
||||
import renderer from "react-test-renderer"
|
||||
|
||||
import { ExternalLink } from "@/components/ExternalLink"
|
||||
import { ExternalLink } from "@/presentation/react/components/ExternalLink"
|
||||
|
||||
describe("<ExternalLink />", () => {
|
||||
it("renders correctly", () => {
|
@ -1,6 +1,6 @@
|
||||
import renderer from "react-test-renderer"
|
||||
|
||||
import { MonoText } from "@/components/MonoText"
|
||||
import { MonoText } from "@/presentation/react/components/MonoText"
|
||||
|
||||
describe("<MonoText />", () => {
|
||||
it("renders correctly", () => {
|
@ -3,12 +3,11 @@ import { createContext, useContext, useEffect } from "react"
|
||||
import type {
|
||||
HabitsTrackerPresenter,
|
||||
HabitsTrackerPresenterState,
|
||||
} from "@/data/infrastructure/presenters/HabitsTrackerPresenter"
|
||||
import { usePresenterState } from "@/hooks/usePresenterState"
|
||||
import { habitsTrackerPresenter } from "@/data/infrastructure"
|
||||
} from "@/presentation/presenters/HabitsTracker"
|
||||
import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
|
||||
import { habitsTrackerPresenter } from "@/infrastructure"
|
||||
|
||||
export interface HabitsTrackerContextValue {
|
||||
habitsTrackerPresenterState: HabitsTrackerPresenterState
|
||||
export interface HabitsTrackerContextValue extends HabitsTrackerPresenterState {
|
||||
habitsTrackerPresenter: HabitsTrackerPresenter
|
||||
}
|
||||
|
||||
@ -36,7 +35,10 @@ export const HabitsTrackerProvider: React.FC<HabitsTrackerProviderProps> = (
|
||||
|
||||
return (
|
||||
<HabitsTrackerContext.Provider
|
||||
value={{ habitsTrackerPresenterState, habitsTrackerPresenter }}
|
||||
value={{
|
||||
...habitsTrackerPresenterState,
|
||||
habitsTrackerPresenter,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</HabitsTrackerContext.Provider>
|
@ -1,6 +1,6 @@
|
||||
import { act, renderHook } from "@testing-library/react-native"
|
||||
|
||||
import { useBoolean } from "@/hooks/useBoolean"
|
||||
import { useBoolean } from "@/presentation/react/hooks/useBoolean"
|
||||
|
||||
describe("hooks/useBoolean", () => {
|
||||
beforeEach(() => {
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import type { Presenter } from "@/data/infrastructure/presenters/_Presenter"
|
||||
import type { Presenter } from "@/presentation/presenters/_Presenter"
|
||||
|
||||
export const usePresenterState = <S>(presenter: Presenter<S>): S => {
|
||||
const [state, setState] = useState<S>(presenter.initialState)
|
@ -1,5 +0,0 @@
|
||||
import "@testing-library/react-native/extend-expect"
|
||||
|
||||
jest.mock("@react-native-async-storage/async-storage", () => {
|
||||
return require("@react-native-async-storage/async-storage/jest/async-storage-mock")
|
||||
})
|