Compare commits

..

11 commits

Author SHA1 Message Date
Christophe Robillard
0a2f26684b fill form mode 2026-03-21 16:17:59 +01:00
Christophe Robillard
a9f72b60af can edit selected day 2026-03-21 16:16:54 +01:00
Christophe Robillard
46e6014fea update day_log 2026-03-20 19:28:54 +01:00
Christophe Robillard
d6fba7e6e8 valid mood unique per day per user 2026-03-20 19:08:26 +01:00
Christophe Robillard
560a9442a7 fix modes factory 2026-03-20 19:08:26 +01:00
Christophe Robillard
93b578c3cf create day_log 2026-03-20 19:08:10 +01:00
Christophe Robillard
5c67b91302 add username to user factory 2026-03-19 21:22:05 +01:00
Christophe Robillard
962915829f add day_log model 2026-03-19 21:22:05 +01:00
Christophe Robillard
497744d165 add image for mode 2026-03-19 19:04:16 +01:00
Christophe Robillard
2ea386e557 show legend based on custom modes 2026-03-15 19:27:36 +01:00
Christophe Robillard
820d31b3e5 replace mood mode string with mode ref 2026-03-15 19:27:25 +01:00
13 changed files with 300 additions and 323 deletions

View file

@ -54,37 +54,13 @@ main {
height: 15px;
}
.day.has-day-log {
position: relative;
}
.day.has-day-log::after {
content: '';
position: absolute;
bottom: 1px;
right: 1px;
width: 4px;
height: 4px;
border-radius: 50%;
background-color: white;
}
.selected-day {
border-radius: 50%;
box-shadow: 0 0 0 3px white, 0 0 0 5px black;
border: 2px double white;
margin: 4px;
width: 15px;
height: 15px;
}
.empty-day {
border: 1px dashed #aaa;
margin: 4px;
width: 15px;
height: 15px;
background: transparent;
}
.info {
margin: 30px;
}

View file

@ -11,7 +11,7 @@ class DayLogsController < ApplicationController
if @day_log.save
handle_mood(@day_log.day, params[:day_log][:mode_id])
redirect_to dashboard_path(day: @day_log.day)
redirect_to root_path
else
render :new, status: :unprocessable_content
end
@ -20,7 +20,7 @@ class DayLogsController < ApplicationController
def update
if @day_log.update(day_log_params)
handle_mood(@day_log.day, params[:day_log][:mode_id])
redirect_to dashboard_path(day: @day_log.day)
redirect_to dashboard_path
else
render :edit, status: :unprocessable_content
end

View file

@ -1,6 +1,5 @@
class MoodsController < ApplicationController
def index
@user = Current.user
redirect_to dashboard_path(day: Date.today) unless params[:day]
end
end

View file

@ -1,19 +1,4 @@
module MoodsHelper
def day_status(mood, user)
return :filled if mood[:mode].present?
return :guessed if mood[:guess].present? && user.guess?
return :unknown if mood[:guess].present? && !user.guess?
:empty
end
def mode_for(mood, user)
case day_status(mood, user)
when :filled then mood[:mode]
when :guessed then mood[:guess]
when :unknown, :empty then Mode.new(label: "unknown", color: "white")
end
end
=begin
def mode_for(mood, user)
if user.guess?
mood[:mode] || mood[:guess] || { label: "unknown", color: "white", image_url: "unknown.jpg" }
@ -21,19 +6,10 @@ module MoodsHelper
mood[:mode] || { label: "unknown", color: "white", image_url: "unknown.jpg" }
end
end
=end
def style_for_mode(mode, status)
case status
when :empty then ""
when :unknown then "background-color: #{mode.color}; border: 2px double grey;"
else "background-color: #{mode.color};"
end
end
def css_class_for_day(status, day_log)
css_class = status == :empty ? "day empty-day" : "day"
css_class += " has-day-log" if day_log
css_class
def style_for_mode(mode)
style = "background-color: #{mode[:color]};"
style += " border: 2px double grey;" if mode[:label] == "unknown"
style
end
end

View file

@ -1,39 +1,18 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = [ "image", "modeDay", "dayLogLink", "dayLogInfo" ];
connect() {
this.lastTarget = null;
const params = new URLSearchParams(window.location.search);
const selectedDay = params.get("day");
if (selectedDay) {
const dayEl = document.querySelector(`[data-day="${selectedDay}"]`);
if (dayEl) {
dayEl.click();
dayEl.scrollIntoView({ block: "center" });
}
} else {
const logs = document.querySelector(".logs");
if (logs) logs.scrollTop = logs.scrollHeight;
}
}
static targets = [ "image", "modeDay", "dayLogLink" ];
updateDayInfo(event) {
const image = this.imageTarget;
const modeDay = this.modeDayTarget;
const day = event.target.dataset.day;
const formattedDay = new Date(day).toLocaleDateString('fr-FR');
const modeDayContent = formattedDay + ' : ' + event.target.dataset.mode;
const modeDayContent = day + ' : ' + event.target.dataset.mode;
image.src = event.target.dataset.image;
modeDay.textContent = modeDayContent;
this.dayLogLinkTarget.href = `/day_logs/edit?day=${day}`;
this.dayLogInfoTarget.textContent = event.target.dataset.info || '';
event.target.className = "selected-day " + event.target.dataset.mode;
history.pushState({}, '', `?day=${day}`);
this.dayLogLinkTarget.href = `/day_logs/edit?day=${day}`
if (this.lastTarget) {
this.lastTarget.className = "day " + this.lastTarget.dataset.mode;
@ -41,4 +20,9 @@ export default class extends Controller {
this.lastTarget = event.target;
}
connect() {
this.lastTarget = null;
window.location = "#end";
}
}

View file

@ -1,8 +1,4 @@
class Mode < ApplicationRecord
belongs_to :user
has_one_attached :image
def image_url
image.attached? ? Rails.application.routes.url_helpers.rails_blob_path(image, only_path: true) : ActionController::Base.helpers.asset_path("unknown.jpg")
end
end

View file

@ -33,14 +33,10 @@ class User < ApplicationRecord
end
def history
MoodCalendarService.generate_calendar(self)
MoodCalendarService.generate_calendar(moods)
end
def current_mode
self.moods.last&.mode || "croisiere"
end
def current_day_log
self.day_logs.find_by(day: Date.today)
end
end

View file

@ -1,10 +1,26 @@
# app/services/mood_calendar_service.rb
class MoodCalendarService
def self.generate_calendar(user, start_date: nil, end_date: nil)
data = user.moods.includes(:mode)
.order(:recorded_at)
.map { |mood| { mode: mood.mode, recorded_at: mood.recorded_at } }
def self.generate_calendar(moods, start_date: nil, end_date: Date.current)
# Preload modes with their image attachments
mode_ids = moods.joins(:mode).pluck("modes.id").uniq
modes_by_id = Mode.where(id: mode_ids)
.with_attached_image
.index_by(&:id)
day_logs_by_date = user.day_logs.index_by(&:day)
data = moods.joins(:mode)
.order(:recorded_at)
.pluck(:recorded_at, "modes.label", "modes.color", "modes.id")
.map do |recorded_at, label, color, mode_id|
mode = modes_by_id[mode_id]
{
mode: {
label: label,
color: color,
image_url: mode&.image&.attached? ? Rails.application.routes.url_helpers.rails_blob_url(mode.image, only_path: true) : nil
},
recorded_at: recorded_at
}
end # Convertir la relation ActiveRecord en tableau de hash
if data.empty?
start_date = Date.current
@ -12,35 +28,36 @@ class MoodCalendarService
start_date ||= data.first[:recorded_at].to_date
end
start_date = start_date.to_date
end_date = end_date ? end_date.to_date : [ Date.current, start_date + 5.months ].max
=begin
if end_date.nil?
if end_date < (start_date + 5.months)
end_date = start_date + 5.months
else
end_date = end_date.to_date
end
=end
# Convertir en Date si ce sont des DateTime ou Time
start_date = start_date.to_date
end_date = end_date&.to_date
# Grouper par jour et garder le plus récent pour chaque jour
data_by_date = data.group_by { |d| d[:recorded_at].to_date }
.transform_values { |entries| entries.max_by { |e| e[:recorded_at] } }
# Trouver le dernier mood avant start_date pour initialiser le guess
last_mode = data.select { |d| d[:recorded_at].to_date < start_date }
.max_by { |d| d[:recorded_at] }
&.[](:mode)
# Générer le tableau complet avec tous les jours
complete_data = (start_date..end_date).map do |date|
day_log = day_logs_by_date[date]
if data_by_date[date]
last_mode = data_by_date[date][:mode]
data_by_date[date].merge(day_log: day_log)
data_by_date[date]
else
{ mode: nil, recorded_at: date.to_datetime, guess: last_mode, day_log: day_log }
{ mode: nil, recorded_at: date.to_datetime, guess: last_mode }
end
end
# Regrouper par mois avec semaines commençant au premier lundi
complete_data.group_by { |d| d[:recorded_at].to_date.beginning_of_month }
.map do |month_start, _|
.map do |month_start, month_data|
first_monday = month_start
first_monday = first_monday.next_occurring(:monday) unless first_monday.monday?
@ -53,7 +70,7 @@ class MoodCalendarService
data_hash = complete_data.index_by { |d| d[:recorded_at].to_date }
all_days = (first_monday..month_end).map do |date|
data_hash[date] || { mode: nil, recorded_at: date.to_datetime, guess: nil, day_log: nil }
data_hash[date] || { mode: nil, recorded_at: date.to_datetime, guess: nil }
end
weeks = all_days.group_by { |d| d[:recorded_at].to_date.beginning_of_week(:monday) }
@ -66,4 +83,4 @@ class MoodCalendarService
}
end
end
end
end#

View file

@ -1,25 +1,20 @@
= form_with model: day_log do |f|
- @day_log.errors.full_messages.each do |msg|
.notification.is-danger= msg
%p= msg
.field
= f.label :info, "Journal", class: "label"
.control
= f.text_area :info, class: "textarea", rows: 5
= f.label :day
= f.date_field :day
.field
= label_tag :mode_id, "Mode", class: "label"
.control
.select.is-fullwidth
= f.label :info
= f.text_area :info
.field
= label_tag :mode_id, "Mode"
= select_tag "day_log[mode_id]",
options_from_collection_for_select(Current.user.modes, :id, :label, @mood&.mode_id),
include_blank: true
.field.is-hidden
= f.date_field :day
= f.submit
.field.is-grouped
.control
= f.submit "Enregistrer", class: "button is-primary"
.control
= link_to "Annuler", dashboard_path(day: @day_log.day), class: "button is-light"

View file

@ -1,13 +1,3 @@
%main.columns.m-auto
.current-day.column.is-hidden-mobile
%section.m-4
%figure.image.has-ratio
= image_tag @mood ? @mood.mode.image_url : "unknown.jpg"
%h1 Modifier le journal
%section.section.column.has-background-primary-light
%h1.title.is-4
%span.icon
%i.fa-regular.fa-calendar
%span= l @day_log.day, format: :long
= render 'form', day_log: @day_log
= render 'form', day_log: @day_log

View file

@ -2,7 +2,7 @@
<div class="current-day column is-hidden-mobile">
<section class="m-4">
<figure class="image has-ratio">
<%= image_tag(@user.current_mode.image_url, "data-mood-target": "image") %>
<%= image_tag(@user.current_mode.image, "data-mood-target": "image") %>
</figure>
</section>
</div>
@ -14,7 +14,7 @@
</div>
<% @user.modes.each do |mode| %>
<div class="is-flex is-align-items-center mr-4">
<div class="bar mr-1" style="<%= style_for_mode(mode, :filled) %>"></div>
<div class="bar mr-1" style="<%= style_for_mode(mode) %>"></div>
<div class=""><%= mode.label %></div>
</div>
<% end %>
@ -23,11 +23,8 @@
<div class="title is-4">
<span class="icon"><i class="fa-regular fa-calendar"></i></span>
<span data-mood-target="modeDay">Aujourd'hui : <%= @user.current_mode.label %></span>
<%= link_to edit_day_log_for_day_path(day: Date.today), "data-mood-target": "dayLogLink" do %>
<span class="icon ml-2"><i class="has-text-grey fa-solid fa-pencil fa-xs"></i></span>
<% end %>
<%= link_to "Journal", edit_day_log_for_day_path(day: Date.today), "data-mood-target": "dayLogLink" %>
</div>
<p data-mood-target="dayLogInfo"><%= @user.current_day_log&.info %></p>
</div>
<div class="logs">
@ -39,23 +36,15 @@
<% month[:weeks].each do |week| %>
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap mb-3">
<% week.each do |mood| %>
<% status = day_status(mood, @user) %>
<% mode = mode_for(mood, @user) %>
<div data-image="<%= mode.image_url %>"
data-mode="<%= mode.label %>"
data-day="<%= mood[:recorded_at].to_date.iso8601 %>"
data-info="<%= mood[:day_log]&.info %>"
data-action="click->mood#updateDayInfo"
title="<%= l(mood[:recorded_at].to_date) %> : <%= mode.label %>"
class="<%= css_class_for_day(status, mood[:day_log]) %>"
style="<%= style_for_mode(mode, status) %>">
</div>
<div data-image="<%= mode[:image_url] %>" data-mode="<%= mode[:label] %>" data-day="<%= l mood[:recorded_at].to_date %>" data-action="click->mood#updateDayInfo" title="<%= l(mood[:recorded_at].to_date) %> : <%= mode[:label] %>" class="day" style="<%= style_for_mode(mode) %>"></div>
<% end %>
</div>
<% end %>
</div>
</div>
<% end %>
<div id="end"></>
</div>
</div>
</div>

View file

@ -1,15 +1,13 @@
class ReplaceMoodModeStringWithModeReference < ActiveRecord::Migration[8.0]
def up
rename_column :moods, :mode, :mode_string
# 1. Ajouter la colonne de référence
add_reference :moods, :mode, foreign_key: true
# 2. Pour chaque mood, trouver ou créer le Mode correspondant
Mood.find_each do |mood|
next if mood.mode_string.blank?
next if mood.mode.blank?
mode_record = Mode.find_or_create_by!(label: mood.mode_string, user_id: mood.user_id) do |m|
mode_record = Mode.find_or_create_by!(label: mood.mode, user_id: mood.user_id) do |m|
m.color = "#000000" # couleur par défaut
end
@ -17,18 +15,16 @@ class ReplaceMoodModeStringWithModeReference < ActiveRecord::Migration[8.0]
end
# 3. Supprimer l'ancienne colonne string
remove_column :moods, :mode_string, :string
remove_column :moods, :mode, :string
end
def down
add_column :moods, :mode_string, :string
add_column :moods, :mode, :string
Mood.find_each do |mood|
mood.update_column(:mode_string, mood.mode&.label)
mood.update_column(:mode, mood.mode&.label)
end
remove_reference :moods, :mode, foreign_key: true
rename_column :moods, :mode_string, :mode
end
end

View file

@ -1,231 +1,330 @@
# spec/services/mood_calendar_service_spec.rb
require 'rails_helper'
RSpec.describe MoodCalendarService do
let(:user) { create(:user) }
let(:mode) { create(:mode, user: user) }
describe '.generate_calendar' do
context 'avec des jours manquants' do
it 'remplit les jours manquants avec mode: nil et guess égal au dernier mode' do
mode2 = create(:mode, user: user)
mode3 = create(:mode, user: user)
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-02-12 14:20:00"))
create(:mood, user: user, mode: mode3, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-18 16:45:00"))
context 'avec un recorded_at pour chaque jour' do
it 'génère un calendrier complet sans jours nil' do
# Données du 1er au 7 février 2025 (une semaine complète)
(1..7).each do |day|
create(:mood, mode: "mood_#{day}", recorded_at: Time.zone.parse("2025-02-0#{day} 10:00:00"))
end
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-02-28")
)
expect(result.length).to be >= 1
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be true
expect(result.length).to eq(1) # Un seul mois
expect(result[0][:month]).to eq(Date.parse("2025-02-01"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
# Première semaine commence le lundi 3 février
first_week = result[0][:weeks][0]
expect(first_week.length).to eq(7) # Du lundi 3 au dimanche 9
# Vérifier que tous les jours du 3 au 7 ont un mode
(3..7).each do |day|
mood = first_week.find { |m| m[:recorded_at].day == day }
expect(mood[:mode]).to eq("mood_#{day}")
expect(mood).not_to have_key(:guess)
end
# Le 8 et 9 février devraient avoir mode: nil avec guess: "mood_7"
mood_8 = first_week.find { |m| m[:recorded_at].day == 8 }
expect(mood_8[:mode]).to be_nil
expect(mood_8[:guess]).to eq("mood_7")
mood_9 = first_week.find { |m| m[:recorded_at].day == 9 }
expect(mood_9[:mode]).to be_nil
expect(mood_9[:guess]).to eq("mood_7")
end
end
context 'avec des jours manquants' do
it 'remplit les jours manquants avec mode: nil et guess égal au dernier mode' do
create(:mood, mode: "triste", recorded_at: Time.zone.parse("2025-02-12 08:30:00"))
create(:mood, mode: "content", recorded_at: Time.zone.parse("2025-02-12 14:20:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "calme", recorded_at: Time.zone.parse("2025-02-18 16:45:00"))
moods = Mood.all
result = MoodCalendarService.generate_calendar(
moods,
end_date: Date.parse("2025-02-28")
)
expect(result.length).to eq(1)
expect(result[0][:month]).to eq(Date.parse("2025-02-01"))
# Trouver tous les jours
all_days = result[0][:weeks].flatten
# Le 12 février devrait avoir le dernier mood (content à 14:20)
mood_12 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-12") }
expect(mood_12[:mode]).to eq(mode2)
expect(mood_12[:mode]).to eq("content")
expect(mood_12[:recorded_at].to_i).to eq(Time.zone.parse("2025-02-12 14:20:00").to_i)
# Le 13 février devrait être nil avec guess: "content"
mood_13 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-13") }
expect(mood_13[:mode]).to be_nil
expect(mood_13[:guess]).to eq(mode2)
expect(mood_13[:guess]).to eq("content")
# Le 14 février devrait être nil avec guess: "content"
mood_14 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-14") }
expect(mood_14[:mode]).to be_nil
expect(mood_14[:guess]).to eq(mode2)
expect(mood_14[:guess]).to eq("content")
# Le 15 février devrait avoir le mood "heureux"
mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(mood_15[:mode]).to eq(mode3)
expect(mood_15[:mode]).to eq("heureux")
# Le 16 février devrait être nil avec guess: "heureux"
mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
expect(mood_16[:mode]).to be_nil
expect(mood_16[:guess]).to eq(mode3)
expect(mood_16[:guess]).to eq("heureux")
# Les jours avant le 12 (à partir du premier lundi 3 février) devraient avoir guess: nil
mood_3 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-03") }
expect(mood_3[:mode]).to be_nil
expect(mood_3[:guess]).to be_nil
end
it 'gère correctement plusieurs mois avec des jours manquants' do
mode2 = create(:mode, user: user)
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-04-20 11:30:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "joyeux", recorded_at: Time.zone.parse("2025-04-20 11:30:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-04-30")
)
expect(result.length).to be >= 3
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be true
expect(result.any? { |m| m[:month] == Date.parse("2025-03-01") }).to be true
expect(result.any? { |m| m[:month] == Date.parse("2025-04-01") }).to be true
# Devrait avoir exactement février, mars et avril
expect(result.length).to eq(3)
# Vérifier février
february = result.find { |m| m[:month] == Date.parse("2025-02-01") }
expect(february).not_to be_nil
# Vérifier mars (tous les jours devraient avoir mode: nil et guess: "heureux")
march = result.find { |m| m[:month] == Date.parse("2025-03-01") }
expect(march).not_to be_nil
all_march_days = march[:weeks].flatten
# Tous les jours de mars (y compris ceux qui débordent début avril) devraient avoir mode: nil
expect(all_march_days.all? { |d| d[:mode].nil? }).to be true
# Tous les jours de mars devraient avoir guess: "heureux"
# (le mois de mars va du 3 mars au 6 avril, avant le mood du 20 avril)
all_march_days.each do |day|
expect(day[:guess]).to be_present,
"Le jour #{day[:recorded_at].to_date} devrait avoir un guess"
expect(day[:guess]).to eq("heureux"),
"Le jour #{day[:recorded_at].to_date} devrait avoir guess: 'heureux' mais a: #{day[:guess].inspect}"
end
# Vérifier avril
april = result.find { |m| m[:month] == Date.parse("2025-04-01") }
expect(april).not_to be_nil
all_april_days = april[:weeks].flatten
mood_20_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-20") }
expect(mood_20_april[:mode]).to eq(mode2)
expect(mood_20_april[:mode]).to eq("joyeux")
# Les jours du 7 au 19 avril devraient avoir guess: "heureux"
mood_10_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-10") }
expect(mood_10_april[:guess]).to eq(mode)
expect(mood_10_april[:guess]).to eq("heureux")
# Les jours après le 20 avril devraient avoir guess: "joyeux"
mood_21_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-21") }
expect(mood_21_april[:guess]).to eq(mode2)
expect(mood_21_april[:guess]).to eq("joyeux")
end
end
context 'sans aucun recorded_at' do
it 'retourne un calendrier à partir de la date courante' do
result = MoodCalendarService.generate_calendar(user)
it 'retourne un tableau vide' do
result = MoodCalendarService.generate_calendar(Mood.none)
expect(result).not_to be_empty
expect(result.first[:month]).to eq(Date.current.beginning_of_month)
expect(result).to eq([])
end
end
context 'avec plusieurs moods le même jour' do
it 'garde uniquement le mood le plus récent' do
create(:mood, mode: "triste", recorded_at: Time.zone.parse("2025-02-12 08:30:00"))
create(:mood, mode: "neutre", recorded_at: Time.zone.parse("2025-02-12 12:00:00"))
create(:mood, mode: "content", recorded_at: Time.zone.parse("2025-02-12 14:20:00"))
create(:mood, mode: "fatigué", recorded_at: Time.zone.parse("2025-02-12 09:15:00"))
moods = Mood.all
result = MoodCalendarService.generate_calendar(
moods,
end_date: Date.parse("2025-02-28")
)
all_days = result[0][:weeks].flatten
mood_12 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-12") }
expect(mood_12[:mode]).to eq("content")
expect(mood_12[:recorded_at].to_i).to eq(Time.zone.parse("2025-02-12 14:20:00").to_i)
end
end
context 'structure du calendrier' do
it 'commence chaque mois par le premier lundi' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-02-28")
)
february = result[0]
first_day = february[:weeks][0][0]
# Le premier jour de février 2025 devrait être le lundi 3 février
expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03"))
expect(first_day[:recorded_at].to_date.monday?).to be true
end
it 'va du premier lundi du mois au dimanche avant le premier lundi du mois suivant' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-03-31")
)
february = result[0]
all_days = february[:weeks].flatten
# Premier jour : lundi 3 février 2025
first_day = all_days.first
expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03"))
expect(first_day[:recorded_at].to_date.monday?).to be true
# Dernier jour : dimanche 2 mars 2025 (veille du premier lundi de mars qui est le 3)
last_day = all_days.last
expect(last_day[:recorded_at].to_date).to eq(Date.parse("2025-03-02"))
expect(last_day[:recorded_at].to_date.sunday?).to be true
# Le 3 mars (premier lundi de mars) ne devrait PAS être dans février
expect(all_days.any? { |d| d[:recorded_at].to_date == Date.parse("2025-03-03") }).to be false
end
it 'toutes les semaines ont exactement 7 jours' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-02-28")
)
february = result[0]
# Toutes les semaines devraient avoir exactement 7 jours
february[:weeks].each do |week|
expect(week.length).to eq(7)
end
end
it 'retourne month comme Date (le premier du mois)' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-02-28")
)
february = result[0]
# month devrait être une Date
expect(february[:month]).to be_a(Date)
expect(february[:month]).to eq(Date.parse("2025-02-01"))
expect(february[:month].day).to eq(1)
expect(february[:month].day).to eq(1) # Premier du mois
end
end
context 'avec paramètres par défaut' do
it "utilise le premier mood comme start_date et aujourd'hui comme end_date" do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
it 'utilise le premier mood comme start_date et aujourd\'hui comme end_date' do
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(user)
result = MoodCalendarService.generate_calendar(Mood.all)
expect(result).not_to be_empty
# Devrait commencer en février
expect(result.first[:month]).to eq(Date.parse("2025-02-01"))
end
end
context 'avec start_date spécifié' do
it 'commence à la date spécifiée' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
start_date: Date.parse("2025-03-01"),
end_date: Date.parse("2025-03-31")
)
# Devrait commencer en mars même si le mood est en février
expect(result.first[:month]).to eq(Date.parse("2025-03-01"))
# Ne devrait pas contenir février
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be false
# Tous les jours de mars devraient avoir guess: "heureux" (du 15 février)
all_march_days = result.first[:weeks].flatten
puts all_march_days.inspect
expect(all_march_days.all? { |d| d[:guess] == "heureux" }).to be true
end
end
context 'avec end_date spécifié' do
it 'se termine à end_date' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
it 'se termine à la date spécifiée' do
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
end_date: Date.parse("2025-09-30")
Mood.all,
end_date: Date.parse("2025-02-28")
)
expect(result.last[:month]).to eq(Date.parse("2025-09-01"))
# Devrait s'arrêter en février
expect(result.last[:month]).to eq(Date.parse("2025-02-01"))
# Ne devrait pas contenir mars ou au-delà
expect(result.any? { |m| m[:month] == Date.parse("2025-03-01") }).to be false
end
end
context 'avec start_date et end_date spécifiés' do
it 'génère le calendrier pour la plage spécifiée' do
mode2 = create(:mode, user: user)
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-04-20 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "calme", recorded_at: Time.zone.parse("2025-04-20 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
start_date: Date.parse("2025-03-01"),
end_date: Date.parse("2025-03-31")
)
# Devrait contenir uniquement mars
expect(result.length).to eq(1)
expect(result.first[:month]).to eq(Date.parse("2025-03-01"))
# Tous les jours de mars devraient avoir guess: "heureux" (du 15 février)
all_days = result.first[:weeks].flatten
expect(all_days.all? { |d| d[:mode].nil? }).to be true
expect(all_days.all? { |d| d[:guess] == "heureux" }).to be true
end
it 'gère une plage de plusieurs mois' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
start_date: Date.parse("2025-02-01"),
end_date: Date.parse("2025-04-30")
)
# Devrait contenir février, mars et avril
months = result.map { |m| m[:month] }
expect(months).to include(
Date.parse("2025-02-01"),
@ -235,10 +334,11 @@ RSpec.describe MoodCalendarService do
end
it 'fonctionne avec DateTime en paramètres' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
# Passer des DateTime au lieu de Date
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
start_date: Time.zone.parse("2025-02-01 00:00:00"),
end_date: Time.zone.parse("2025-02-28 23:59:59")
)
@ -250,109 +350,72 @@ RSpec.describe MoodCalendarService do
context 'avec une plage ne contenant aucun mood' do
it 'génère un calendrier avec tous les jours à nil et guess du dernier mood avant la plage' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
start_date: Date.parse("2025-04-01"),
end_date: Date.parse("2025-04-30")
)
# Devrait contenir avril
expect(result.first[:month]).to eq(Date.parse("2025-04-01"))
# Tous les jours devraient avoir mode: nil et guess: "heureux"
all_days = result.first[:weeks].flatten
expect(all_days.all? { |d| d[:mode].nil? }).to be true
expect(all_days.all? { |d| d[:guess] == "heureux" }).to be true
end
end
context 'avec plusieurs mois' do
it 'chaque mois commence et finit correctement' do
mode2 = create(:mode, user: user)
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-03-20 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "calme", recorded_at: Time.zone.parse("2025-03-20 10:00:00"))
result = MoodCalendarService.generate_calendar(
user,
Mood.all,
end_date: Date.parse("2025-03-31")
)
february = result.find { |m| m[:month] == Date.parse("2025-02-01") }
march = result.find { |m| m[:month] == Date.parse("2025-03-01") }
# Février se termine le dimanche 2 mars
last_day_february = february[:weeks].flatten.last
expect(last_day_february[:recorded_at].to_date).to eq(Date.parse("2025-03-02"))
# Mars commence le lundi 3 mars
first_day_march = march[:weeks].flatten.first
expect(first_day_march[:recorded_at].to_date).to eq(Date.parse("2025-03-03"))
# Pas de chevauchement
expect(last_day_february[:recorded_at].to_date + 1.day).to eq(first_day_march[:recorded_at].to_date)
end
end
context 'avec un scope spécifique' do
it 'fonctionne avec des moods filtrés par user' do
user1 = create(:user)
user2 = create(:user)
mode2 = create(:mode, user: user2)
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user2, mode: mode2, recorded_at: Time.zone.parse("2025-02-16 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"), user: user1)
create(:mood, mode: "triste", recorded_at: Time.zone.parse("2025-02-16 10:00:00"), user: user2)
moods = Mood.where(user: user1)
result = MoodCalendarService.generate_calendar(
user,
moods,
end_date: Date.parse("2025-02-28")
)
all_days = result[0][:weeks].flatten
mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(mood_15[:mode]).to eq(mode)
expect(mood_15[:mode]).to eq("heureux")
# Le mood du user2 ne devrait pas apparaître
mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
expect(mood_16[:mode]).to be_nil
expect(mood_16[:guess]).to eq(mode)
end
end
context 'avec day_log' do
it 'inclut le day_log dans les données du jour correspondant' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
day_log = create(:day_log, user: user, day: Date.parse("2025-02-15"), info: "Une belle journée")
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(day_15[:day_log]).to eq(day_log)
end
it 'retourne nil pour day_log quand il n\'en existe pas pour ce jour' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(day_15[:day_log]).to be_nil
end
it 'inclut le day_log pour un jour sans mood' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
day_log = create(:day_log, user: user, day: Date.parse("2025-02-16"), info: "Jour sans mood")
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
expect(day_16[:mode]).to be_nil
expect(day_16[:day_log]).to eq(day_log)
end
it 'inclut le day_log pour un jour avec mood' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
day_log = create(:day_log, user: user, day: Date.parse("2025-02-15"), info: "Jour avec mood")
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(day_15[:mode]).to eq(mode)
expect(day_15[:day_log]).to eq(day_log)
expect(mood_16[:guess]).to eq("heureux")
end
end
end