diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55b7d3e..131392d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,8 @@ jobs: version: 10 - name: Install npm modules run: cd src/evently.client && pnpm i + - name: Run UI Tests + run: cd src/evently.client && pnpm run test - name: Restore dependencies run: dotnet restore src/Evently.Server/Evently.Server.csproj - name: Build diff --git a/Makefile b/Makefile index 0ee4aca..bc1b4f6 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ test: dotnet test ./tests/Evently.Server.Test/VisualPatron.Server.Test.csproj add-migration: - dotnet ef migrations add SeedInit --project=src/Evently.Server --context=AppDbContext --output-dir=Common/Adapters/Data/Migrations + dotnet ef migrations add RenameAccountId --project=src/Evently.Server --context=AppDbContext --output-dir=Common/Adapters/Data/Migrations update-migration: dotnet ef database update --project=src/Evently.Server --context=AppDbContext diff --git a/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs b/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs index 47b873e..62766ca 100644 --- a/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs +++ b/src/Evently.Server/Common/Adapters/Data/AppDbContext.cs @@ -243,7 +243,7 @@ private static void SeedData(ModelBuilder modelBuilder) { new Booking { BookingId = "book_abc123456", GatheringId = 1, - AccountId = guestUserId, + AttendeeId = guestUserId, CreationDateTime = fixedCreationTime, CheckInDateTime = null, CheckoutDateTime = null, @@ -252,7 +252,7 @@ private static void SeedData(ModelBuilder modelBuilder) { new Booking { BookingId = "book_def789012", GatheringId = 2, - AccountId = hostUserId, + AttendeeId = hostUserId, CreationDateTime = fixedCreationTime.AddHours(1), // Slightly different time CheckInDateTime = null, CheckoutDateTime = null, diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/20250827143433_RenameAccountId.Designer.cs b/src/Evently.Server/Common/Adapters/Data/Migrations/20250827143433_RenameAccountId.Designer.cs new file mode 100644 index 0000000..37c949c --- /dev/null +++ b/src/Evently.Server/Common/Adapters/Data/Migrations/20250827143433_RenameAccountId.Designer.cs @@ -0,0 +1,774 @@ +// +using System; +using Evently.Server.Common.Adapters.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Evently.Server.Common.Adapters.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250827143433_RenameAccountId")] + partial class RenameAccountId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Account", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("LogoSrc") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + + b.HasData( + new + { + Id = "empty-user-12345", + AccessFailedCount = 0, + ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", + Email = "empty@example.com", + EmailConfirmed = false, + LockoutEnabled = true, + Name = "Empty User", + NormalizedEmail = "EMPTY@EXAMPLE.COM", + NormalizedUserName = "EMPTY_USER", + PhoneNumberConfirmed = false, + SecurityStamp = "EMPTY-SECURITY-STAMP-12345", + TwoFactorEnabled = false, + UserName = "empty_user" + }, + new + { + Id = "guest-user-22222", + AccessFailedCount = 0, + ConcurrencyStamp = "EMPTY-CONCURRENCY-STAMP-12345", + Email = "guest@example.com", + EmailConfirmed = false, + LockoutEnabled = true, + Name = "Guest User", + NormalizedEmail = "GUEST@EXAMPLE.COM", + NormalizedUserName = "GUEST_USER_2", + PhoneNumberConfirmed = false, + SecurityStamp = "EMPTY-SECURITY-STAMP-12345", + TwoFactorEnabled = false, + UserName = "guest_user2" + }); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Booking", b => + { + b.Property("BookingId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("AttendeeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CancellationDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CheckInDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CheckoutDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("GatheringId") + .HasColumnType("bigint"); + + b.HasKey("BookingId"); + + b.HasIndex("AttendeeId"); + + b.HasIndex("GatheringId"); + + b.ToTable("Bookings"); + + b.HasData( + new + { + BookingId = "book_abc123456", + AttendeeId = "guest-user-22222", + CreationDateTime = new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + GatheringId = 1L + }, + new + { + BookingId = "book_def789012", + AttendeeId = "empty-user-12345", + CreationDateTime = new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + GatheringId = 2L + }); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CategoryId")); + NpgsqlPropertyBuilderExtensions.HasIdentityOptions(b.Property("CategoryId"), 20L, null, null, null, null, null); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("CategoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("CategoryId"); + + b.ToTable("Categories"); + + b.HasData( + new + { + CategoryId = 1L, + Approved = false, + CategoryName = "Information Technology" + }, + new + { + CategoryId = 2L, + Approved = false, + CategoryName = "Business & Networking" + }, + new + { + CategoryId = 3L, + Approved = false, + CategoryName = "Arts & Culture" + }); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Gathering", b => + { + b.Property("GatheringId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GatheringId")); + NpgsqlPropertyBuilderExtensions.HasIdentityOptions(b.Property("GatheringId"), 20L, null, null, null, null, null); + + b.Property("CancellationDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CoverSrc") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("End") + .HasColumnType("timestamp with time zone"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganiserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Start") + .HasColumnType("timestamp with time zone"); + + b.HasKey("GatheringId"); + + b.ToTable("Gatherings"); + + b.HasData( + new + { + GatheringId = 1L, + CoverSrc = "", + Description = "A comprehensive summit exploring the latest in AI and machine learning", + End = new DateTimeOffset(new DateTime(2025, 12, 5, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Marina Bay Sands Convention Centre, Singapore", + Name = "Tech Innovation Summit", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2025, 12, 5, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 2L, + CoverSrc = "", + Description = "Connect with fellow entrepreneurs and investors", + End = new DateTimeOffset(new DateTime(2025, 12, 10, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Clarke Quay Central, Singapore", + Name = "Startup Networking Night", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2025, 12, 10, 18, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 3L, + CoverSrc = "", + Description = "Showcasing contemporary digital art from emerging artists", + End = new DateTimeOffset(new DateTime(2025, 12, 15, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "National Gallery Singapore", + Name = "Digital Art Exhibition", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2025, 12, 15, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 4L, + CoverSrc = "", + Description = "Learn modern web development techniques and best practices", + End = new DateTimeOffset(new DateTime(2025, 12, 8, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Singapore Science Centre", + Name = "Web Development Workshop", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2025, 12, 8, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 5L, + CoverSrc = "", + Description = "Advanced strategies for scaling your business", + End = new DateTimeOffset(new DateTime(2025, 12, 20, 16, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Raffles City Convention Centre, Singapore", + Name = "Business Strategy Seminar", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2025, 12, 20, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 6L, + CoverSrc = "", + Description = "Professional photography techniques and portfolio building", + End = new DateTimeOffset(new DateTime(2025, 12, 22, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Gardens by the Bay, Singapore", + Name = "Photography Masterclass", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2025, 12, 22, 8, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 7L, + CoverSrc = "", + Description = "Intensive bootcamp covering iOS and Android development", + End = new DateTimeOffset(new DateTime(2025, 12, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "NUS School of Computing, Singapore", + Name = "Mobile App Development Bootcamp", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2025, 12, 12, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 8L, + CoverSrc = "", + Description = "Learn about personal finance and investment strategies", + End = new DateTimeOffset(new DateTime(2025, 12, 25, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Suntec Singapore Convention Centre", + Name = "Investment & Finance Forum", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2025, 12, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 9L, + CoverSrc = "", + Description = "Explore storytelling techniques and creative expression", + End = new DateTimeOffset(new DateTime(2025, 12, 28, 15, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Esplanade Theatres, Singapore", + Name = "Creative Writing Workshop", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2025, 12, 28, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 10L, + CoverSrc = "", + Description = "Latest trends in cloud architecture and DevOps", + End = new DateTimeOffset(new DateTime(2025, 12, 30, 17, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Singapore EXPO", + Name = "Cloud Computing Conference", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2025, 12, 30, 9, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 11L, + CoverSrc = "", + Description = "Build and scale your online business effectively", + End = new DateTimeOffset(new DateTime(2026, 1, 3, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Marina Bay Financial Centre, Singapore", + Name = "E-commerce Mastery", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2026, 1, 3, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 12L, + CoverSrc = "", + Description = "An evening of modern dance and artistic expression", + End = new DateTimeOffset(new DateTime(2026, 1, 5, 22, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Victoria Theatre, Singapore", + Name = "Contemporary Dance Performance", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2026, 1, 5, 19, 30, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 13L, + CoverSrc = "", + Description = "Essential cybersecurity practices for businesses", + End = new DateTimeOffset(new DateTime(2026, 1, 8, 16, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Singapore Management University", + Name = "Cybersecurity Awareness Training", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2026, 1, 8, 10, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 14L, + CoverSrc = "", + Description = "Develop essential leadership skills for modern managers", + End = new DateTimeOffset(new DateTime(2026, 1, 10, 17, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Orchard Hotel Singapore", + Name = "Leadership Excellence Workshop", + OrganiserId = "empty-user-12345", + Start = new DateTimeOffset(new DateTime(2026, 1, 10, 9, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }, + new + { + GatheringId = 15L, + CoverSrc = "", + Description = "Independent filmmakers present their latest works", + End = new DateTimeOffset(new DateTime(2026, 1, 12, 23, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + Location = "Singapore International Film Festival Venue", + Name = "Film & Media Production Showcase", + OrganiserId = "guest-user-22222", + Start = new DateTimeOffset(new DateTime(2026, 1, 12, 18, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)) + }); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.GatheringCategoryDetail", b => + { + b.Property("GatheringId") + .HasColumnType("bigint"); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.HasKey("GatheringId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("GatheringCategoryDetails"); + + b.HasData( + new + { + GatheringId = 1L, + CategoryId = 1L + }, + new + { + GatheringId = 2L, + CategoryId = 2L + }, + new + { + GatheringId = 3L, + CategoryId = 3L + }, + new + { + GatheringId = 4L, + CategoryId = 1L + }, + new + { + GatheringId = 5L, + CategoryId = 2L + }, + new + { + GatheringId = 6L, + CategoryId = 3L + }, + new + { + GatheringId = 7L, + CategoryId = 1L + }, + new + { + GatheringId = 8L, + CategoryId = 2L + }, + new + { + GatheringId = 9L, + CategoryId = 3L + }, + new + { + GatheringId = 10L, + CategoryId = 1L + }, + new + { + GatheringId = 11L, + CategoryId = 2L + }, + new + { + GatheringId = 12L, + CategoryId = 3L + }, + new + { + GatheringId = 13L, + CategoryId = 1L + }, + new + { + GatheringId = 14L, + CategoryId = 2L + }, + new + { + GatheringId = 15L, + CategoryId = 3L + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Booking", b => + { + b.HasOne("Evently.Server.Common.Domains.Entities.Account", "Account") + .WithMany("Bookings") + .HasForeignKey("AttendeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Evently.Server.Common.Domains.Entities.Gathering", "Gathering") + .WithMany("Bookings") + .HasForeignKey("GatheringId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Gathering"); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.GatheringCategoryDetail", b => + { + b.HasOne("Evently.Server.Common.Domains.Entities.Category", "Category") + .WithMany("GatheringCategoryDetails") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Evently.Server.Common.Domains.Entities.Gathering", "Gathering") + .WithMany("GatheringCategoryDetails") + .HasForeignKey("GatheringId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Gathering"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Evently.Server.Common.Domains.Entities.Account", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Evently.Server.Common.Domains.Entities.Account", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Evently.Server.Common.Domains.Entities.Account", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Evently.Server.Common.Domains.Entities.Account", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Account", b => + { + b.Navigation("Bookings"); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Category", b => + { + b.Navigation("GatheringCategoryDetails"); + }); + + modelBuilder.Entity("Evently.Server.Common.Domains.Entities.Gathering", b => + { + b.Navigation("Bookings"); + + b.Navigation("GatheringCategoryDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/20250827143433_RenameAccountId.cs b/src/Evently.Server/Common/Adapters/Data/Migrations/20250827143433_RenameAccountId.cs new file mode 100644 index 0000000..ec31611 --- /dev/null +++ b/src/Evently.Server/Common/Adapters/Data/Migrations/20250827143433_RenameAccountId.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Evently.Server.Common.Adapters.Data.Migrations +{ + /// + public partial class RenameAccountId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Bookings_AspNetUsers_AccountId", + table: "Bookings"); + + migrationBuilder.RenameColumn( + name: "AccountId", + table: "Bookings", + newName: "AttendeeId"); + + migrationBuilder.RenameIndex( + name: "IX_Bookings_AccountId", + table: "Bookings", + newName: "IX_Bookings_AttendeeId"); + + migrationBuilder.AddForeignKey( + name: "FK_Bookings_AspNetUsers_AttendeeId", + table: "Bookings", + column: "AttendeeId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Bookings_AspNetUsers_AttendeeId", + table: "Bookings"); + + migrationBuilder.RenameColumn( + name: "AttendeeId", + table: "Bookings", + newName: "AccountId"); + + migrationBuilder.RenameIndex( + name: "IX_Bookings_AttendeeId", + table: "Bookings", + newName: "IX_Bookings_AccountId"); + + migrationBuilder.AddForeignKey( + name: "FK_Bookings_AspNetUsers_AccountId", + table: "Bookings", + column: "AccountId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs b/src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs index 91a9d3c..f3b010e 100644 --- a/src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Evently.Server/Common/Adapters/Data/Migrations/AppDbContextModelSnapshot.cs @@ -135,7 +135,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(50) .HasColumnType("character varying(50)"); - b.Property("AccountId") + b.Property("AttendeeId") .IsRequired() .HasMaxLength(100) .HasColumnType("character varying(100)"); @@ -157,7 +157,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("BookingId"); - b.HasIndex("AccountId"); + b.HasIndex("AttendeeId"); b.HasIndex("GatheringId"); @@ -167,14 +167,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { BookingId = "book_abc123456", - AccountId = "guest-user-22222", + AttendeeId = "guest-user-22222", CreationDateTime = new DateTimeOffset(new DateTime(2024, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), GatheringId = 1L }, new { BookingId = "book_def789012", - AccountId = "empty-user-12345", + AttendeeId = "empty-user-12345", CreationDateTime = new DateTimeOffset(new DateTime(2024, 1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), GatheringId = 2L }); @@ -664,7 +664,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("Evently.Server.Common.Domains.Entities.Account", "Account") .WithMany("Bookings") - .HasForeignKey("AccountId") + .HasForeignKey("AttendeeId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); diff --git a/src/Evently.Server/Common/Domains/Entities/Booking.cs b/src/Evently.Server/Common/Domains/Entities/Booking.cs index 7c65792..19cd259 100644 --- a/src/Evently.Server/Common/Domains/Entities/Booking.cs +++ b/src/Evently.Server/Common/Domains/Entities/Booking.cs @@ -16,7 +16,7 @@ public class Booking { [StringLength(50)] public string BookingId { get; set; } = $"book_{Nanoid.Generate(size: 10)}"; - [StringLength(100)] [ForeignKey("Account")] public string AccountId { get; set; } = string.Empty; + [StringLength(100)] [ForeignKey("Account")] public string AttendeeId { get; set; } = string.Empty; [JsonIgnore] public Account? Account { get; set; } [NotMapped] public AccountDto? AccountDto => Account?.ToAccountDto(); diff --git a/src/Evently.Server/Common/Extensions/MapperExtension.cs b/src/Evently.Server/Common/Extensions/MapperExtension.cs index 93e0874..01d0eee 100644 --- a/src/Evently.Server/Common/Extensions/MapperExtension.cs +++ b/src/Evently.Server/Common/Extensions/MapperExtension.cs @@ -38,7 +38,7 @@ public static GatheringReqDto ToGatheringDto(this Gathering gathering) { public static Booking ToBooking(this BookingReqDto bookingReqDto) { return new Booking { BookingId = bookingReqDto.BookingId, - AccountId = bookingReqDto.AttendeeId, + AttendeeId = bookingReqDto.AttendeeId, GatheringId = bookingReqDto.GatheringId, CreationDateTime = bookingReqDto.CreationDateTime, CheckInDateTime = bookingReqDto.CheckInDateTime, @@ -50,7 +50,7 @@ public static Booking ToBooking(this BookingReqDto bookingReqDto) { public static BookingReqDto ToBookingDto(this Booking booking) { return new BookingReqDto( booking.BookingId, - booking.AccountId, + booking.AttendeeId, booking.GatheringId, booking.CreationDateTime, booking.CheckInDateTime, diff --git a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs index 252c88f..f5aa168 100644 --- a/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs +++ b/src/Evently.Server/Features/Accounts/Services/AccountValidator.cs @@ -8,7 +8,7 @@ public AccountValidator() { RuleFor((member) => member.Name).NotEmpty().WithMessage("Name is required."); RuleFor((member) => member.Email).NotEmpty().WithMessage("Email is required."); RuleForEach((member) => member.Bookings).Custom((value, context) => { - if (value.AccountId == string.Empty) { + if (value.AttendeeId == string.Empty) { context.AddFailure("MemberId is required."); } diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index 8e38982..08868be 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -32,7 +32,7 @@ public async Task> GetBookings(string? accountId, long? gath DateTimeOffset? gatheringStartBefore, DateTimeOffset? gatheringStartAfter, DateTimeOffset? gatheringEndBefore, DateTimeOffset? gatheringEndAfter, bool? isCancelled, int? offset, int? limit) { IQueryable query = db.Bookings - .Where((b) => accountId == null || b.AccountId == accountId) + .Where((b) => accountId == null || b.AttendeeId == accountId) .Where((b) => gatheringId == null || b.GatheringId == gatheringId) .Where((c) => checkInStart == null || checkInStart <= c.CheckInDateTime) .Where((b) => checkInEnd == null || b.CheckInDateTime <= checkInEnd) @@ -89,7 +89,7 @@ public async Task UpdateBooking(string bookingId, BookingReqDto booking .FirstOrDefaultAsync((be) => be.BookingId == bookingId) ?? throw new KeyNotFoundException($"{booking.BookingId} not found"); - current.AccountId = booking.AccountId; + current.AttendeeId = booking.AttendeeId; current.GatheringId = booking.GatheringId; current.CreationDateTime = booking.CreationDateTime; current.CheckInDateTime = booking.CheckInDateTime; diff --git a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs index 80944bf..5cf6c2f 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingValidator.cs @@ -6,6 +6,6 @@ namespace Evently.Server.Features.Bookings.Services; public sealed class BookingValidator : AbstractValidator { public BookingValidator() { RuleFor((booking) => booking.GatheringId).NotEmpty().WithMessage("GatheringId is required."); - RuleFor((booking) => booking.AccountId).NotEmpty().WithMessage("AccountId is required."); + RuleFor((booking) => booking.AttendeeId).NotEmpty().WithMessage("AccountId is required."); } } \ No newline at end of file diff --git a/src/Evently.Server/Features/Emails/Views/Ticket.razor b/src/Evently.Server/Features/Emails/Views/Ticket.razor index bc26845..14f886f 100644 --- a/src/Evently.Server/Features/Emails/Views/Ticket.razor +++ b/src/Evently.Server/Features/Emails/Views/Ticket.razor @@ -25,7 +25,7 @@ + style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;"> diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs index 114b863..aa43552 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs @@ -38,8 +38,8 @@ public async Task> GetGatherings( .Where((gathering) => organiserId == null || gathering.OrganiserId == organiserId) .Where(gathering => isCancelled == null || gathering.CancellationDateTime.HasValue == isCancelled) .Where((gathering) => - attendeeId == null || gathering.Bookings.Any((be) => be.AccountId == attendeeId)) - .Include(gathering => gathering.Bookings.Where((be) => be.AccountId == attendeeId)) + attendeeId == null || gathering.Bookings.Any((be) => be.AttendeeId == attendeeId)) + .Include(gathering => gathering.Bookings.Where((be) => be.AttendeeId == attendeeId)) .Include(gathering => gathering.GatheringCategoryDetails) .ThenInclude(detail => detail.Category); diff --git a/src/evently.client/index.html b/src/evently.client/index.html index 0d4fdc9..5bd53ac 100644 --- a/src/evently.client/index.html +++ b/src/evently.client/index.html @@ -3,7 +3,10 @@ - + Evently diff --git a/src/evently.client/package.json b/src/evently.client/package.json index 5b310a7..fee64ab 100644 --- a/src/evently.client/package.json +++ b/src/evently.client/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite", + "test": "vitest", "clean": "pnpm dlx npkill", "build": "tsc -b && vite build", "fmt": "prettier --write . && eslint .", @@ -36,6 +37,10 @@ "@eslint/js": "^9.32.0", "@iconify/react": "^6.0.0", "@tanstack/router-plugin": "^1.131.7", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", "@types/lodash.clonedeep": "^4.5.9", "@types/lodash.uniqby": "^4.7.9", "@types/luxon": "^3.7.1", @@ -52,6 +57,8 @@ "prettier-plugin-tailwindcss": "^0.6.14", "typescript": "~5.8.3", "typescript-eslint": "^8.39.0", - "vite": "^7.1.0" + "vite": "^7.1.0", + "vitest": "^3.2.4", + "vitest-dom": "^0.1.1" } } diff --git a/src/evently.client/pnpm-lock.yaml b/src/evently.client/pnpm-lock.yaml index ddcf221..16158e1 100644 --- a/src/evently.client/pnpm-lock.yaml +++ b/src/evently.client/pnpm-lock.yaml @@ -77,6 +77,18 @@ importers: "@tanstack/router-plugin": specifier: ^1.131.7 version: 1.131.7(@tanstack/react-router@1.131.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4)) + "@testing-library/dom": + specifier: ^10.4.1 + version: 10.4.1 + "@testing-library/jest-dom": + specifier: ^6.8.0 + version: 6.8.0 + "@testing-library/react": + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@testing-library/user-event": + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) "@types/lodash.clonedeep": specifier: ^4.5.9 version: 4.5.9 @@ -128,8 +140,20 @@ importers: vite: specifier: ^7.1.0 version: 7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@22.17.1)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.4) + vitest-dom: + specifier: ^0.1.1 + version: 0.1.1(vitest@3.2.4(@types/node@22.17.1)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.4)) packages: + "@adobe/css-tools@4.4.4": + resolution: + { + integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg== + } + "@ampproject/remapping@2.3.0": resolution: { @@ -137,6 +161,12 @@ packages: } engines: { node: ">=6.0.0" } + "@asamuzakjp/css-color@3.2.0": + resolution: + { + integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw== + } + "@babel/code-frame@7.27.1": resolution: { @@ -347,6 +377,13 @@ packages: peerDependencies: "@babel/core": ^7.0.0-0 + "@babel/runtime@7.28.3": + resolution: + { + integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA== + } + engines: { node: ">=6.9.0" } + "@babel/template@7.27.2": resolution: { @@ -368,6 +405,49 @@ packages: } engines: { node: ">=6.9.0" } + "@csstools/color-helpers@5.1.0": + resolution: + { + integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== + } + engines: { node: ">=18" } + + "@csstools/css-calc@2.1.4": + resolution: + { + integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== + } + engines: { node: ">=18" } + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-color-parser@3.1.0": + resolution: + { + integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== + } + engines: { node: ">=18" } + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-parser-algorithms@3.0.5": + resolution: + { + integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + } + engines: { node: ">=18" } + peerDependencies: + "@csstools/css-tokenizer": ^3.0.4 + + "@csstools/css-tokenizer@3.0.4": + resolution: + { + integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== + } + engines: { node: ">=18" } + "@esbuild/aix-ppc64@0.25.8": resolution: { @@ -1212,6 +1292,53 @@ packages: } engines: { node: ">=12" } + "@testing-library/dom@10.4.1": + resolution: + { + integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg== + } + engines: { node: ">=18" } + + "@testing-library/jest-dom@6.8.0": + resolution: + { + integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ== + } + engines: { node: ">=14", npm: ">=6", yarn: ">=1" } + + "@testing-library/react@16.3.0": + resolution: + { + integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw== + } + engines: { node: ">=18" } + peerDependencies: + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@testing-library/user-event@14.6.1": + resolution: + { + integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== + } + engines: { node: ">=12", npm: ">=6" } + peerDependencies: + "@testing-library/dom": ">=7.21.4" + + "@types/aria-query@5.0.4": + resolution: + { + integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + } + "@types/babel__core@7.20.5": resolution: { @@ -1236,6 +1363,18 @@ packages: integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== } + "@types/chai@5.2.2": + resolution: + { + integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== + } + + "@types/deep-eql@4.0.2": + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + } + "@types/estree@1.0.8": resolution: { @@ -1402,6 +1541,56 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + "@vitest/expect@3.2.4": + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== + } + + "@vitest/mocker@3.2.4": + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== + } + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + "@vitest/pretty-format@3.2.4": + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== + } + + "@vitest/runner@3.2.4": + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== + } + + "@vitest/snapshot@3.2.4": + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ== + } + + "@vitest/spy@3.2.4": + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== + } + + "@vitest/utils@3.2.4": + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== + } + acorn-jsx@5.3.2: resolution: { @@ -1418,6 +1607,13 @@ packages: engines: { node: ">=0.4.0" } hasBin: true + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + } + engines: { node: ">= 14" } + ajv@6.12.6: resolution: { @@ -1438,6 +1634,13 @@ packages: } engines: { node: ">=8" } + ansi-styles@5.2.0: + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + } + engines: { node: ">=10" } + ansis@4.1.0: resolution: { @@ -1458,6 +1661,19 @@ packages: integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + aria-query@5.3.0: + resolution: + { + integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + } + + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + } + engines: { node: ">=12" } + ast-types@0.16.1: resolution: { @@ -1529,6 +1745,13 @@ packages: engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + } + engines: { node: ">=8" } + call-bind-apply-helpers@1.0.2: resolution: { @@ -1556,6 +1779,13 @@ packages: integrity: sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A== } + chai@5.3.3: + resolution: + { + integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + } + engines: { node: ">=18" } + chalk@4.1.2: resolution: { @@ -1563,6 +1793,20 @@ packages: } engines: { node: ">=10" } + chalk@5.6.0: + resolution: + { + integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ== + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + check-error@2.1.1: + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + } + engines: { node: ">= 16" } + chokidar@3.6.0: resolution: { @@ -1635,6 +1879,19 @@ packages: } engines: { node: ">= 8" } + css.escape@1.5.1: + resolution: + { + integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + } + + cssstyle@4.6.0: + resolution: + { + integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg== + } + engines: { node: ">=18" } + csstype@3.1.3: resolution: { @@ -1647,6 +1904,13 @@ packages: integrity: sha512-c1PweK5RI1C76q58FKvbS4jzgyNJSP6CGTQ+KkZYzADdJoERnOxFoeLfDHmQgxLpjEzlYhFMXCeodQNLCC9bow== } + data-urls@5.0.0: + resolution: + { + integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + } + engines: { node: ">=18" } + debug@4.4.1: resolution: { @@ -1666,6 +1930,12 @@ packages: } engines: { node: ">=0.10.0" } + decimal.js@10.6.0: + resolution: + { + integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + } + decode-formdata@0.9.0: resolution: { @@ -1679,6 +1949,13 @@ packages: } engines: { node: ">= 16" } + deep-eql@5.0.2: + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + } + engines: { node: ">=6" } + deep-is@0.1.4: resolution: { @@ -1692,6 +1969,13 @@ packages: } engines: { node: ">=0.4.0" } + dequal@2.0.3: + resolution: + { + integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + } + engines: { node: ">=6" } + detect-libc@2.0.4: resolution: { @@ -1725,6 +2009,18 @@ packages: } engines: { node: ">=16" } + dom-accessibility-api@0.5.16: + resolution: + { + integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + } + + dom-accessibility-api@0.6.3: + resolution: + { + integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + } + dunder-proto@1.0.1: resolution: { @@ -1751,6 +2047,13 @@ packages: } engines: { node: ">=10.13.0" } + entities@6.0.1: + resolution: + { + integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + } + engines: { node: ">=0.12" } + es-define-property@1.0.1: resolution: { @@ -1765,6 +2068,12 @@ packages: } engines: { node: ">= 0.4" } + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + } + es-object-atoms@1.1.1: resolution: { @@ -1888,6 +2197,12 @@ packages: } engines: { node: ">=4.0" } + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + } + esutils@2.0.3: resolution: { @@ -1895,6 +2210,13 @@ packages: } engines: { node: ">=0.10.0" } + expect-type@1.2.2: + resolution: + { + integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA== + } + engines: { node: ">=12.0.0" } + fast-deep-equal@3.1.3: resolution: { @@ -2128,6 +2450,34 @@ packages: } engines: { node: ">= 0.4" } + html-encoding-sniffer@4.0.0: + resolution: + { + integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + } + engines: { node: ">=18" } + + http-proxy-agent@7.0.2: + resolution: + { + integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + } + engines: { node: ">= 14" } + + https-proxy-agent@7.0.6: + resolution: + { + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + } + engines: { node: ">= 14" } + + iconv-lite@0.6.3: + resolution: + { + integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + } + engines: { node: ">=0.10.0" } + ignore@5.3.2: resolution: { @@ -2156,6 +2506,20 @@ packages: } engines: { node: ">=0.8.19" } + indent-string@4.0.0: + resolution: + { + integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + } + engines: { node: ">=8" } + + indent-string@5.0.0: + resolution: + { + integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== + } + engines: { node: ">=12" } + is-binary-path@2.1.0: resolution: { @@ -2191,6 +2555,12 @@ packages: } engines: { node: ">=0.12.0" } + is-potential-custom-element-name@1.0.1: + resolution: + { + integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + } + isbot@5.1.29: resolution: { @@ -2217,6 +2587,12 @@ packages: integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + } + js-yaml@4.1.0: resolution: { @@ -2224,6 +2600,18 @@ packages: } hasBin: true + jsdom@26.1.0: + resolution: + { + integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg== + } + engines: { node: ">=18" } + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: { @@ -2389,6 +2777,12 @@ packages: } engines: { node: ">=10" } + lodash-es@4.17.21: + resolution: + { + integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + } + lodash.clonedeep@4.5.0: resolution: { @@ -2413,20 +2807,39 @@ packages: integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== } - lru-cache@5.1.1: + loupe@3.2.1: resolution: { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== } - luxon@3.7.1: + lru-cache@10.4.3: resolution: { - integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg== + integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== } - engines: { node: ">=12" } - magic-string@0.30.17: + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + } + + luxon@3.7.1: + resolution: + { + integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg== + } + engines: { node: ">=12" } + + lz-string@1.5.0: + resolution: + { + integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + } + hasBin: true + + magic-string@0.30.17: resolution: { integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== @@ -2467,6 +2880,13 @@ packages: } engines: { node: ">= 0.6" } + min-indent@1.0.1: + resolution: + { + integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + } + engines: { node: ">=4" } + minimatch@3.1.2: resolution: { @@ -2535,6 +2955,12 @@ packages: } engines: { node: ">=0.10.0" } + nwsapi@2.2.21: + resolution: + { + integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA== + } + optionator@0.9.4: resolution: { @@ -2584,6 +3010,12 @@ packages: } engines: { node: ">=6" } + parse5@7.3.0: + resolution: + { + integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + } + path-exists@4.0.0: resolution: { @@ -2598,6 +3030,19 @@ packages: } engines: { node: ">=8" } + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + } + + pathval@2.0.1: + resolution: + { + integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + } + engines: { node: ">= 14.16" } + picocolors@1.1.1: resolution: { @@ -2711,6 +3156,13 @@ packages: engines: { node: ">=14" } hasBin: true + pretty-format@27.5.1: + resolution: + { + integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + } + engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + proxy-from-env@1.1.0: resolution: { @@ -2752,6 +3204,12 @@ packages: peerDependencies: react: ^19.1.1 + react-is@17.0.2: + resolution: + { + integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + } + react-refresh@0.17.0: resolution: { @@ -2780,6 +3238,20 @@ packages: } engines: { node: ">= 4" } + redent@3.0.0: + resolution: + { + integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + } + engines: { node: ">=8" } + + redent@4.0.0: + resolution: + { + integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag== + } + engines: { node: ">=12" } + reflect-metadata@0.2.2: resolution: { @@ -2827,12 +3299,31 @@ packages: engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true + rrweb-cssom@0.8.0: + resolution: + { + integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw== + } + run-parallel@1.2.0: resolution: { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + } + + saxes@6.0.0: + resolution: + { + integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + } + engines: { node: ">=v12.22.7" } + scheduler@0.26.0: resolution: { @@ -2890,6 +3381,12 @@ packages: } engines: { node: ">=8" } + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + } + solid-js@1.9.8: resolution: { @@ -2917,6 +3414,18 @@ packages: } engines: { node: ">= 12" } + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + } + + std-env@3.9.0: + resolution: + { + integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== + } + string-width@4.2.3: resolution: { @@ -2931,6 +3440,20 @@ packages: } engines: { node: ">=8" } + strip-indent@3.0.0: + resolution: + { + integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + } + engines: { node: ">=8" } + + strip-indent@4.0.0: + resolution: + { + integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA== + } + engines: { node: ">=12" } + strip-json-comments@3.1.1: resolution: { @@ -2938,6 +3461,12 @@ packages: } engines: { node: ">=8" } + strip-literal@3.0.0: + resolution: + { + integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA== + } + supports-color@7.2.0: resolution: { @@ -2945,6 +3474,12 @@ packages: } engines: { node: ">=8" } + symbol-tree@3.2.4: + resolution: + { + integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + } + tailwindcss@4.1.11: resolution: { @@ -2977,6 +3512,18 @@ packages: integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== } + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + } + + tinyexec@0.3.2: + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + } + tinyglobby@0.2.14: resolution: { @@ -2984,6 +3531,40 @@ packages: } engines: { node: ">=12.0.0" } + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + } + engines: { node: ^18.0.0 || >=20.0.0 } + + tinyrainbow@2.0.0: + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== + } + engines: { node: ">=14.0.0" } + + tinyspy@4.0.3: + resolution: + { + integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A== + } + engines: { node: ">=14.0.0" } + + tldts-core@6.1.86: + resolution: + { + integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== + } + + tldts@6.1.86: + resolution: + { + integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== + } + hasBin: true + to-regex-range@5.0.1: resolution: { @@ -2991,6 +3572,20 @@ packages: } engines: { node: ">=8.0" } + tough-cookie@5.1.2: + resolution: + { + integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== + } + engines: { node: ">=16" } + + tr46@5.1.1: + resolution: + { + integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw== + } + engines: { node: ">=18" } + ts-api-utils@2.1.0: resolution: { @@ -3090,6 +3685,14 @@ packages: integrity: sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng== } + vite-node@3.2.4: + resolution: + { + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg== + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + vite@7.1.2: resolution: { @@ -3133,12 +3736,86 @@ packages: yaml: optional: true + vitest-dom@0.1.1: + resolution: + { + integrity: sha512-n/bonR2hcRHCE5hlzG/P0yTXTUXx/gPtsaeUWP86ADfwo/+dHDpnTTV14qY7+kevsUbOZFYECu77MXY7AA0QSA== + } + peerDependencies: + vitest: ">=0.31.0" + + vitest@3.2.4: + resolution: + { + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: + { + integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + } + engines: { node: ">=18" } + + webidl-conversions@7.0.0: + resolution: + { + integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + } + engines: { node: ">=12" } + webpack-virtual-modules@0.6.2: resolution: { integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== } + whatwg-encoding@3.1.1: + resolution: + { + integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + } + engines: { node: ">=18" } + + whatwg-mimetype@4.0.0: + resolution: + { + integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + } + engines: { node: ">=18" } + + whatwg-url@14.2.0: + resolution: + { + integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw== + } + engines: { node: ">=18" } + which-module@2.0.1: resolution: { @@ -3153,6 +3830,14 @@ packages: engines: { node: ">= 8" } hasBin: true + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + } + engines: { node: ">=8" } + hasBin: true + word-wrap@1.2.5: resolution: { @@ -3167,6 +3852,34 @@ packages: } engines: { node: ">=8" } + ws@8.18.3: + resolution: + { + integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: + { + integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + } + engines: { node: ">=18" } + + xmlchars@2.2.0: + resolution: + { + integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + } + y18n@4.0.3: resolution: { @@ -3214,11 +3927,22 @@ packages: } snapshots: + "@adobe/css-tools@4.4.4": {} + "@ampproject/remapping@2.3.0": dependencies: "@jridgewell/gen-mapping": 0.3.13 "@jridgewell/trace-mapping": 0.3.30 + "@asamuzakjp/css-color@3.2.0": + dependencies: + "@csstools/css-calc": 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-color-parser": 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 + lru-cache: 10.4.3 + optional: true + "@babel/code-frame@7.27.1": dependencies: "@babel/helper-validator-identifier": 7.27.1 @@ -3392,6 +4116,8 @@ snapshots: transitivePeerDependencies: - supports-color + "@babel/runtime@7.28.3": {} + "@babel/template@7.27.2": dependencies: "@babel/code-frame": 7.27.1 @@ -3415,6 +4141,31 @@ snapshots: "@babel/helper-string-parser": 7.27.1 "@babel/helper-validator-identifier": 7.27.1 + "@csstools/color-helpers@5.1.0": + optional: true + + "@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)": + dependencies: + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 + optional: true + + "@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)": + dependencies: + "@csstools/color-helpers": 5.1.0 + "@csstools/css-calc": 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + "@csstools/css-parser-algorithms": 3.0.5(@csstools/css-tokenizer@3.0.4) + "@csstools/css-tokenizer": 3.0.4 + optional: true + + "@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)": + dependencies: + "@csstools/css-tokenizer": 3.0.4 + optional: true + + "@csstools/css-tokenizer@3.0.4": + optional: true + "@esbuild/aix-ppc64@0.25.8": optional: true @@ -3843,6 +4594,42 @@ snapshots: "@tanstack/virtual-file-routes@1.131.2": {} + "@testing-library/dom@10.4.1": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/runtime": 7.28.3 + "@types/aria-query": 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + "@testing-library/jest-dom@6.8.0": + dependencies: + "@adobe/css-tools": 4.4.4 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + "@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@babel/runtime": 7.28.3 + "@testing-library/dom": 10.4.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.10 + "@types/react-dom": 19.1.7(@types/react@19.1.10) + + "@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)": + dependencies: + "@testing-library/dom": 10.4.1 + + "@types/aria-query@5.0.4": {} + "@types/babel__core@7.20.5": dependencies: "@babel/parser": 7.28.0 @@ -3864,6 +4651,12 @@ snapshots: dependencies: "@babel/types": 7.28.2 + "@types/chai@5.2.2": + dependencies: + "@types/deep-eql": 4.0.2 + + "@types/deep-eql@4.0.2": {} + "@types/estree@1.0.8": {} "@types/json-schema@7.0.15": {} @@ -4003,12 +4796,57 @@ snapshots: transitivePeerDependencies: - supports-color + "@vitest/expect@3.2.4": + dependencies: + "@types/chai": 5.2.2 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + "@vitest/mocker@3.2.4(vite@7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4))": + dependencies: + "@vitest/spy": 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4) + + "@vitest/pretty-format@3.2.4": + dependencies: + tinyrainbow: 2.0.0 + + "@vitest/runner@3.2.4": + dependencies: + "@vitest/utils": 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + "@vitest/snapshot@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + "@vitest/spy@3.2.4": + dependencies: + tinyspy: 4.0.3 + + "@vitest/utils@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + agent-base@7.1.4: + optional: true + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4022,6 +4860,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansis@4.1.0: {} anymatch@3.1.3: @@ -4031,6 +4871,12 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + ast-types@0.16.1: dependencies: tslib: 2.8.1 @@ -4082,6 +4928,8 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4093,11 +4941,23 @@ snapshots: caniuse-lite@1.0.30001734: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.0: {} + + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4142,24 +5002,45 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css.escape@1.5.1: {} + + cssstyle@4.6.0: + dependencies: + "@asamuzakjp/css-color": 3.2.0 + rrweb-cssom: 0.8.0 + optional: true + csstype@3.1.3: {} daisyui@5.0.50: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + optional: true + debug@4.4.1: dependencies: ms: 2.1.3 decamelize@1.2.0: {} + decimal.js@10.6.0: + optional: true + decode-formdata@0.9.0: {} deeks@3.1.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} delayed-stream@1.0.0: {} + dequal@2.0.3: {} + detect-libc@2.0.4: {} devalue@5.1.1: {} @@ -4170,6 +5051,10 @@ snapshots: doc-path@4.1.1: {} + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4185,10 +5070,15 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 + entities@6.0.1: + optional: true + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -4310,8 +5200,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + "@types/estree": 1.0.8 + esutils@2.0.3: {} + expect-type@1.2.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -4434,6 +5330,32 @@ snapshots: dependencies: function-bind: 1.1.2 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4445,6 +5367,10 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + + indent-string@5.0.0: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -4459,6 +5385,9 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: + optional: true + isbot@5.1.29: {} isexe@2.0.0: {} @@ -4467,10 +5396,40 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.21 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + optional: true + jsesc@3.1.0: {} json-2-csv@5.5.9: @@ -4548,6 +5507,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash.clonedeep@4.5.0: {} lodash.debounce@4.0.8: {} @@ -4556,12 +5517,19 @@ snapshots: lodash.uniqby@4.7.0: {} + loupe@3.2.1: {} + + lru-cache@10.4.3: + optional: true + lru-cache@5.1.1: dependencies: yallist: 3.1.1 luxon@3.7.1: {} + lz-string@1.5.0: {} + magic-string@0.30.17: dependencies: "@jridgewell/sourcemap-codec": 1.5.5 @@ -4581,6 +5549,8 @@ snapshots: dependencies: mime-db: 1.52.0 + min-indent@1.0.1: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -4607,6 +5577,9 @@ snapshots: normalize-path@3.0.0: {} + nwsapi@2.2.21: + optional: true + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4638,10 +5611,19 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + optional: true + path-exists@4.0.0: {} path-key@3.1.1: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -4664,6 +5646,12 @@ snapshots: prettier@3.6.2: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + proxy-from-env@1.1.0: {} punycode@2.3.1: {} @@ -4685,6 +5673,8 @@ snapshots: react: 19.1.1 scheduler: 0.26.0 + react-is@17.0.2: {} + react-refresh@0.17.0: {} react@19.1.1: {} @@ -4701,6 +5691,16 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redent@4.0.0: + dependencies: + indent-string: 5.0.0 + strip-indent: 4.0.0 + reflect-metadata@0.2.2: {} require-directory@2.1.1: {} @@ -4739,10 +5739,21 @@ snapshots: "@rollup/rollup-win32-x64-msvc": 4.46.2 fsevents: 2.3.3 + rrweb-cssom@0.8.0: + optional: true + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: + optional: true + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + optional: true + scheduler@0.26.0: {} semver@6.3.1: {} @@ -4763,6 +5774,8 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + solid-js@1.9.8: dependencies: csstype: 3.1.3 @@ -4775,6 +5788,10 @@ snapshots: source-map@0.7.6: {} + stackback@0.0.2: {} + + std-env@3.9.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4785,12 +5802,27 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-indent@4.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: + optional: true + tailwindcss@4.1.11: {} tapable@2.2.2: {} @@ -4808,15 +5840,43 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + tldts-core@6.1.86: + optional: true + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + optional: true + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + optional: true + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + optional: true + ts-api-utils@2.1.0(typescript@5.8.3): dependencies: typescript: 5.8.3 @@ -4876,6 +5936,27 @@ snapshots: uzip@0.20201231.0: {} + vite-node@3.2.4(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4) + transitivePeerDependencies: + - "@types/node" + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4): dependencies: esbuild: 0.25.8 @@ -4891,14 +5972,93 @@ snapshots: lightningcss: 1.30.1 tsx: 4.20.4 + vitest-dom@0.1.1(vitest@3.2.4(@types/node@22.17.1)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.4)): + dependencies: + aria-query: 5.3.0 + chalk: 5.6.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash-es: 4.17.21 + redent: 4.0.0 + vitest: 3.2.4(@types/node@22.17.1)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.4) + + vitest@3.2.4(@types/node@22.17.1)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.4): + dependencies: + "@types/chai": 5.2.2 + "@vitest/expect": 3.2.4 + "@vitest/mocker": 3.2.4(vite@7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4)) + "@vitest/pretty-format": 3.2.4 + "@vitest/runner": 3.2.4 + "@vitest/snapshot": 3.2.4 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.3.3 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.1.2(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4) + vite-node: 3.2.4(@types/node@22.17.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.4) + why-is-node-running: 2.3.0 + optionalDependencies: + "@types/node": 22.17.1 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + optional: true + + webidl-conversions@7.0.0: + optional: true + webpack-virtual-modules@0.6.2: {} + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + optional: true + + whatwg-mimetype@4.0.0: + optional: true + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + optional: true + which-module@2.0.1: {} which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -4907,6 +6067,15 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + ws@8.18.3: + optional: true + + xml-name-validator@5.0.0: + optional: true + + xmlchars@2.2.0: + optional: true + y18n@4.0.3: {} yallist@3.1.1: {} diff --git a/src/evently.client/src/lib/components/-card.test.tsx b/src/evently.client/src/lib/components/-card.test.tsx new file mode 100644 index 0000000..c85118f --- /dev/null +++ b/src/evently.client/src/lib/components/-card.test.tsx @@ -0,0 +1,88 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { Card } from "~/lib/components/card.tsx"; +import { Gathering } from "~/lib/domains/entities"; +import { getMockGathering } from "~/lib/services/gathering-service.mock"; +import { TestWrapper, WrapperDataTestId } from "~/lib/components/test-wrapper.tsx"; + +describe("Card Component", () => { + let mockGathering: Gathering; + beforeEach(async () => { + mockGathering = await getMockGathering(1); + }); + + it("renders gathering name", async () => { + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + expect(screen.getByText("Tech Conference 2024")).toBeInTheDocument(); + }); + + it("renders gathering description", async () => { + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + + expect( + screen.getByText("Annual technology conference with industry leaders") + ).toBeInTheDocument(); + }); + + it("renders gathering location", async () => { + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + + expect(screen.getByText("Convention Center")).toBeInTheDocument(); + }); + + it("renders cover image when coverSrc is provided", async () => { + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + + const image = screen.getByRole("img"); + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute("src", "/images/tech-conference.jpg"); + }); + + it("displays cancelled status when gathering is cancelled", async () => { + const cancelledGathering = { + ...mockGathering, + cancellationDateTime: new Date("2024-12-10T10:00:00Z") + }; + + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + expect(screen.getByText(/cancelled/i)).toBeInTheDocument(); + }); + + it("handles gathering with multiple categories", async () => { + const gatheringWithMultipleCategories: Gathering = await getMockGathering(1); + + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + + expect(screen.getByText("Technology")).toBeInTheDocument(); + expect(screen.getByText("Networking")).toBeInTheDocument(); + }); +}); diff --git a/src/evently.client/src/lib/components/dock.tsx b/src/evently.client/src/lib/components/dock.tsx deleted file mode 100644 index aec67eb..0000000 --- a/src/evently.client/src/lib/components/dock.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { type JSX } from "react"; -import { Link } from "@tanstack/react-router"; - -export function Dock(): JSX.Element { - return ( -
- - Gatherings - - - Attending - - - Hosting - -
- ); -} diff --git a/src/evently.client/src/lib/components/index.ts b/src/evently.client/src/lib/components/index.ts index 9ab2e02..c557861 100644 --- a/src/evently.client/src/lib/components/index.ts +++ b/src/evently.client/src/lib/components/index.ts @@ -1,6 +1,7 @@ export { Navbar } from "./navbar"; -export { Dock } from "./dock"; export { Card } from "./card"; -export * from "./tabs"; +export { Tabs } from "./tabs"; export * from "./tab-state.ts"; export { FieldErrMsg } from "./field-err-msg.tsx"; +export { TestWrapper, WrapperDataTestId } from "./test-wrapper.tsx"; +export * from "./render-with-test-providers.tsx"; diff --git a/src/evently.client/src/lib/components/navbar.tsx b/src/evently.client/src/lib/components/navbar.tsx index 0f59d41..fe71da0 100644 --- a/src/evently.client/src/lib/components/navbar.tsx +++ b/src/evently.client/src/lib/components/navbar.tsx @@ -32,27 +32,25 @@ export function Navbar(): JSX.Element { return (
-
- {/* Back button (only show when not on home page) */} - {!isHomePage && ( - - )} -
+ {/* Back button (only show when not on home page) */} + {!isHomePage && ( + + )} {/* Brand/Logo */} - + Evently
{/* Desktop menu */} -
+
  • diff --git a/src/evently.client/src/lib/components/render-with-test-providers.tsx b/src/evently.client/src/lib/components/render-with-test-providers.tsx new file mode 100644 index 0000000..47b1125 --- /dev/null +++ b/src/evently.client/src/lib/components/render-with-test-providers.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter +} from "@tanstack/react-router"; +import { render, screen } from "@testing-library/react"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; + +type RenderOptions = { + pathPattern: string; + initialEntry?: string; +}; + +/** + * Taken from https://dev.to/saltorgil/testing-tanstack-router-4io3 + * Renders a component under: + * - a minimal TanStack Router instance (memory history), + * - optionally wrapped in a QueryClientProvider. + * + * If `initialEntry` is omitted, it defaults to `pathPattern`. + * + * @param Component The React component to mount. + * @param opts Render options. + * @returns { router, renderResult } + */ + +export async function renderWithTestProviders( + Component: React.ComponentType, + { pathPattern, initialEntry = pathPattern }: RenderOptions +) { + // Root route with minimal Outlet for rendering child routes + const rootRoute = createRootRoute({ + component: () => ( + <> +
    + + + ) + }); + + // Index route so '/' always matches + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: () =>
    Index
    + }); + + // Test route mounting your Component at the dynamic path + const testRoute = createRoute({ + getParentRoute: () => rootRoute, + path: pathPattern, + component: () => + }); + + // Create the router instance with memory history + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute, testRoute]), + history: createMemoryHistory({ initialEntries: [initialEntry] }), + defaultPendingMinMs: 0 + }); + + const queryClient = new QueryClient(); + // Build the render tree and add QueryClientProvider if provided + let tree = ; + tree = {tree}; + + // Render and wait for the route to resolve and the component to mount + const renderResult = render(tree); + await screen.findByTestId("root-layout"); + + return { router, renderResult }; +} diff --git a/src/evently.client/src/lib/components/test-wrapper.tsx b/src/evently.client/src/lib/components/test-wrapper.tsx new file mode 100644 index 0000000..f07f351 --- /dev/null +++ b/src/evently.client/src/lib/components/test-wrapper.tsx @@ -0,0 +1,59 @@ +import type { JSX, ReactNode } from "react"; +import { Account } from "~/lib/domains/entities"; +import { + createRootRouteWithContext, + createRoute, + createRouter, + Outlet, + RouterProvider +} from "@tanstack/react-router"; +import type { RouteContext } from "~/lib/domains/interfaces"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +interface Props { + children: ReactNode; +} + +export const WrapperDataTestId = "root-layout"; + +/** + * A HOC to wire up the components with TanStack configurations so that it can be tested. + * + * Remember to wait for the wrapped Component to be rendered by calling: `await waitFor(() => screen.findByTestId(WrapperDataTestId))` + * @param children The React Component to be tested + * @constructor + */ +export function TestWrapper({ children }: Props): JSX.Element { + const rootRoute = createRootRouteWithContext()({ + beforeLoad: async () => { + const account: Account | null = new Account(); + return { account }; + }, + component: () => ( +
    + +
    + ) + }); + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: () => <>{children} + }); + + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + defaultPendingMinMs: 0, + context: { + account: undefined! + } + }); + const queryClient = new QueryClient(); + + return ( + + + + ); +} diff --git a/src/evently.client/src/lib/domains/entities/entities.ts b/src/evently.client/src/lib/domains/entities/entities.ts index 0d5203e..6e835fc 100644 --- a/src/evently.client/src/lib/domains/entities/entities.ts +++ b/src/evently.client/src/lib/domains/entities/entities.ts @@ -30,14 +30,15 @@ export class Account { } export class Booking { - public bookingId = ""; - public accountDto = new Account(); - public gatheringId = 0; - public gathering = new Gathering(); - public creationDateTime = new Date(); - public checkInDateTime: Date | null = null; - public checkoutDateTime: Date | null = null; - public cancellationDateTime: Date | null = null; + bookingId = ""; + attendeeId = ""; + accountDto = new Account(); + gatheringId = 0; + gathering = new Gathering(); + creationDateTime = new Date(); + checkInDateTime: Date | null = null; + checkoutDateTime: Date | null = null; + cancellationDateTime: Date | null = null; constructor(partial: Partial = {}) { Object.assign(this, partial); @@ -45,8 +46,8 @@ export class Booking { } export class Category { - public categoryId = 0; - public categoryName = ""; + categoryId = 0; + categoryName = ""; constructor(partial: Partial = {}) { // Apply partial properties using Object.assign @@ -55,10 +56,10 @@ export class Category { } export class GatheringCategoryDetail { - public gatheringId = 0; - public categoryId = 0; - public gathering = new Gathering(); - public category = new Category(); + gatheringId = 0; + categoryId = 0; + gathering = new Gathering(); + category = new Category(); constructor(partial: Partial = {}) { // Apply partial properties using Object.assign diff --git a/src/evently.client/src/lib/domains/interfaces/index.ts b/src/evently.client/src/lib/domains/interfaces/index.ts new file mode 100644 index 0000000..3642323 --- /dev/null +++ b/src/evently.client/src/lib/domains/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from "./route-context.ts"; +export * from "./page-result.ts"; diff --git a/src/evently.client/src/lib/domains/models/page-result.ts b/src/evently.client/src/lib/domains/interfaces/page-result.ts similarity index 100% rename from src/evently.client/src/lib/domains/models/page-result.ts rename to src/evently.client/src/lib/domains/interfaces/page-result.ts diff --git a/src/evently.client/src/lib/domains/interfaces/route-context.ts b/src/evently.client/src/lib/domains/interfaces/route-context.ts new file mode 100644 index 0000000..e8c5610 --- /dev/null +++ b/src/evently.client/src/lib/domains/interfaces/route-context.ts @@ -0,0 +1,6 @@ +import { Account } from "~/lib/domains/entities"; + +export interface RouteContext { + // The ReturnType of your useAuth hook or the value of your AuthContext + account: Account; +} diff --git a/src/evently.client/src/lib/domains/models/index.ts b/src/evently.client/src/lib/domains/models/index.ts index 805a71a..270ecda 100644 --- a/src/evently.client/src/lib/domains/models/index.ts +++ b/src/evently.client/src/lib/domains/models/index.ts @@ -1,3 +1,2 @@ export * from "./upsert-dtos.ts"; export * from "./toast-content.ts"; -export * from "./page-result.ts"; diff --git a/src/evently.client/src/lib/services/booking-service.ts b/src/evently.client/src/lib/services/booking-service.ts index 6204c48..a77ea3c 100644 --- a/src/evently.client/src/lib/services/booking-service.ts +++ b/src/evently.client/src/lib/services/booking-service.ts @@ -1,6 +1,7 @@ import axios from "axios"; import { Booking } from "~/lib/domains/entities"; -import { BookingReqDto, type PageResult } from "~/lib/domains/models"; +import { BookingReqDto } from "~/lib/domains/models"; +import type { PageResult } from "~/lib/domains/interfaces"; export interface GetBookingsParams { attendeeId?: string; diff --git a/src/evently.client/src/lib/services/gathering-service.mock.ts b/src/evently.client/src/lib/services/gathering-service.mock.ts new file mode 100644 index 0000000..30029ab --- /dev/null +++ b/src/evently.client/src/lib/services/gathering-service.mock.ts @@ -0,0 +1,119 @@ +import { Booking, Gathering, Category } from "~/lib/domains/entities"; +import { GatheringReqDto } from "~/lib/domains/models"; +import type { GetGatheringsParams } from "./gathering-service"; +import type { PageResult } from "~/lib/domains/interfaces"; + +// Mock data for categories +const mockCategories: Category[] = [ + new Category({ + categoryId: 1, + categoryName: "Technology" + }), + new Category({ + categoryId: 2, + categoryName: "Networking" + }) +]; + +// Mock data for gatherings +const mockGatherings: Gathering[] = [ + { + gatheringId: 1, + name: "Tech Conference 2024", + description: "Annual technology conference with industry leaders", + start: new Date("2024-12-15T09:00:00Z"), + end: new Date("2024-12-15T17:00:00Z"), + location: "Convention Center", + organiserId: "org-123", + cancellationDateTime: null, + coverSrc: "/images/tech-conference.jpg", + bookings: [{ ...new Booking(), attendeeId: "abc" }], + gatheringCategoryDetails: [ + { + gatheringId: 1, + categoryId: 1, + category: mockCategories[0], + gathering: new Gathering() + }, + { + gatheringId: 2, + categoryId: 2, + category: mockCategories[1], + gathering: new Gathering() + } + ] + }, + { + gatheringId: 2, + name: "Design Workshop", + description: "Interactive design thinking workshop", + start: new Date("2024-12-20T14:00:00Z"), + end: new Date("2024-12-20T18:00:00Z"), + location: "Creative Studio", + organiserId: "org-456", + cancellationDateTime: null, + coverSrc: "/images/design-workshop.jpg", + bookings: [], + gatheringCategoryDetails: [] + }, + { + gatheringId: 3, + name: "Networking Event", + description: "Professional networking event for developers", + start: new Date("2025-01-10T18:00:00Z"), + end: new Date("2025-01-10T21:00:00Z"), + location: "Downtown Hub", + organiserId: "org-789", + cancellationDateTime: null, + coverSrc: "/images/networking.jpg", + bookings: [], + gatheringCategoryDetails: [ + { + gatheringId: 2, + categoryId: 3, + category: mockCategories[1], + gathering: new Gathering() + } + ] + } +]; + +export async function getMockGathering(id: number): Promise { + return mockGatherings.find((g) => g.gatheringId === id)!; +} + +export async function getMockGatherings( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _params: GetGatheringsParams = {} +): Promise> { + // Simulate network delay + await new Promise((resolve) => setTimeout(resolve, 300)); + return { + totalCount: 10, + data: [...mockGatherings] + }; +} + +export async function createMockGathering( + gatheringDto: GatheringReqDto, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _coverImg?: File | null +): Promise { + return { + ...new Gathering(), + ...gatheringDto + }; +} + +export async function updateMockGathering( + gatheringId: number, + gatheringDto: GatheringReqDto, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _coverImg?: File | null +): Promise { + return { + ...new Gathering(), + ...gatheringDto, + gatheringId: gatheringId + }; +} diff --git a/src/evently.client/src/lib/services/gathering-service.ts b/src/evently.client/src/lib/services/gathering-service.ts index 3952bec..cc3c1d6 100644 --- a/src/evently.client/src/lib/services/gathering-service.ts +++ b/src/evently.client/src/lib/services/gathering-service.ts @@ -1,6 +1,7 @@ import type { Gathering } from "~/lib/domains/entities"; import axios from "axios"; -import { GatheringReqDto, type PageResult } from "~/lib/domains/models"; +import { GatheringReqDto } from "~/lib/domains/models"; +import type { PageResult } from "~/lib/domains/interfaces"; export interface GetGatheringsParams { attendeeId?: string; @@ -14,6 +15,7 @@ export interface GetGatheringsParams { offset?: number; limit?: number; } + export async function getGatherings(params: GetGatheringsParams): Promise> { const response = await axios.get("/api/v1/Gatherings", { params }); const gatherings: Gathering[] = response.data; diff --git a/src/evently.client/src/routeTree.gen.ts b/src/evently.client/src/routeTree.gen.ts index dde4da1..65e370f 100644 --- a/src/evently.client/src/routeTree.gen.ts +++ b/src/evently.client/src/routeTree.gen.ts @@ -8,351 +8,328 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from './routes/__root' -import { Route as IndexRouteImport } from './routes/index' -import { Route as LoginIndexRouteImport } from './routes/login/index' -import { Route as HealthcheckIndexRouteImport } from './routes/healthcheck/index' -import { Route as GatheringsIndexRouteImport } from './routes/gatherings/index' -import { Route as LoginCallbackRouteImport } from './routes/login/callback' -import { Route as authGatheringsRouteRouteImport } from './routes/(auth)/gatherings/route' -import { Route as authBookingsRouteRouteImport } from './routes/(auth)/bookings/route' -import { Route as GatheringsGatheringIdIndexRouteImport } from './routes/gatherings/$gatheringId/index' -import { Route as authGatheringsCreateRouteImport } from './routes/(auth)/gatherings/create' -import { Route as authBookingsHostingIndexRouteImport } from './routes/(auth)/bookings/hosting/index' -import { Route as authBookingsAttendingIndexRouteImport } from './routes/(auth)/bookings/attending/index' -import { Route as authGatheringsGatheringIdUpdateRouteImport } from './routes/(auth)/gatherings/$gatheringId/update' -import { Route as authBookingsHostingGatheringIdDashboardIndexRouteImport } from './routes/(auth)/bookings/hosting/$gatheringId/dashboard.index' -import { Route as authBookingsHostingGatheringIdDashboardScanRouteImport } from './routes/(auth)/bookings/hosting/$gatheringId/dashboard.scan' +import { Route as rootRouteImport } from "./routes/__root"; +import { Route as IndexRouteImport } from "./routes/index"; +import { Route as LoginIndexRouteImport } from "./routes/login/index"; +import { Route as HealthcheckIndexRouteImport } from "./routes/healthcheck/index"; +import { Route as GatheringsIndexRouteImport } from "./routes/gatherings/index"; +import { Route as authGatheringsRouteRouteImport } from "./routes/(auth)/gatherings/route"; +import { Route as authBookingsRouteRouteImport } from "./routes/(auth)/bookings/route"; +import { Route as GatheringsGatheringIdIndexRouteImport } from "./routes/gatherings/$gatheringId/index"; +import { Route as authGatheringsCreateRouteImport } from "./routes/(auth)/gatherings/create"; +import { Route as authBookingsHostingIndexRouteImport } from "./routes/(auth)/bookings/hosting/index"; +import { Route as authBookingsAttendingIndexRouteImport } from "./routes/(auth)/bookings/attending/index"; +import { Route as authGatheringsGatheringIdUpdateRouteImport } from "./routes/(auth)/gatherings/$gatheringId/update"; +import { Route as authBookingsHostingGatheringIdDashboardIndexRouteImport } from "./routes/(auth)/bookings/hosting/$gatheringId/dashboard.index"; +import { Route as authBookingsHostingGatheringIdDashboardScanRouteImport } from "./routes/(auth)/bookings/hosting/$gatheringId/dashboard.scan"; const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/", + path: "/", + getParentRoute: () => rootRouteImport +} as any); const LoginIndexRoute = LoginIndexRouteImport.update({ - id: '/login/', - path: '/login/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/login/", + path: "/login/", + getParentRoute: () => rootRouteImport +} as any); const HealthcheckIndexRoute = HealthcheckIndexRouteImport.update({ - id: '/healthcheck/', - path: '/healthcheck/', - getParentRoute: () => rootRouteImport, -} as any) + id: "/healthcheck/", + path: "/healthcheck/", + getParentRoute: () => rootRouteImport +} as any); const GatheringsIndexRoute = GatheringsIndexRouteImport.update({ - id: '/gatherings/', - path: '/gatherings/', - getParentRoute: () => rootRouteImport, -} as any) -const LoginCallbackRoute = LoginCallbackRouteImport.update({ - id: '/login/callback', - path: '/login/callback', - getParentRoute: () => rootRouteImport, -} as any) + id: "/gatherings/", + path: "/gatherings/", + getParentRoute: () => rootRouteImport +} as any); const authGatheringsRouteRoute = authGatheringsRouteRouteImport.update({ - id: '/(auth)/gatherings', - path: '/gatherings', - getParentRoute: () => rootRouteImport, -} as any) + id: "/(auth)/gatherings", + path: "/gatherings", + getParentRoute: () => rootRouteImport +} as any); const authBookingsRouteRoute = authBookingsRouteRouteImport.update({ - id: '/(auth)/bookings', - path: '/bookings', - getParentRoute: () => rootRouteImport, -} as any) -const GatheringsGatheringIdIndexRoute = - GatheringsGatheringIdIndexRouteImport.update({ - id: '/gatherings/$gatheringId/', - path: '/gatherings/$gatheringId/', - getParentRoute: () => rootRouteImport, - } as any) + id: "/(auth)/bookings", + path: "/bookings", + getParentRoute: () => rootRouteImport +} as any); +const GatheringsGatheringIdIndexRoute = GatheringsGatheringIdIndexRouteImport.update({ + id: "/gatherings/$gatheringId/", + path: "/gatherings/$gatheringId/", + getParentRoute: () => rootRouteImport +} as any); const authGatheringsCreateRoute = authGatheringsCreateRouteImport.update({ - id: '/create', - path: '/create', - getParentRoute: () => authGatheringsRouteRoute, -} as any) -const authBookingsHostingIndexRoute = - authBookingsHostingIndexRouteImport.update({ - id: '/hosting/', - path: '/hosting/', - getParentRoute: () => authBookingsRouteRoute, - } as any) -const authBookingsAttendingIndexRoute = - authBookingsAttendingIndexRouteImport.update({ - id: '/attending/', - path: '/attending/', - getParentRoute: () => authBookingsRouteRoute, - } as any) -const authGatheringsGatheringIdUpdateRoute = - authGatheringsGatheringIdUpdateRouteImport.update({ - id: '/$gatheringId/update', - path: '/$gatheringId/update', - getParentRoute: () => authGatheringsRouteRoute, - } as any) + id: "/create", + path: "/create", + getParentRoute: () => authGatheringsRouteRoute +} as any); +const authBookingsHostingIndexRoute = authBookingsHostingIndexRouteImport.update({ + id: "/hosting/", + path: "/hosting/", + getParentRoute: () => authBookingsRouteRoute +} as any); +const authBookingsAttendingIndexRoute = authBookingsAttendingIndexRouteImport.update({ + id: "/attending/", + path: "/attending/", + getParentRoute: () => authBookingsRouteRoute +} as any); +const authGatheringsGatheringIdUpdateRoute = authGatheringsGatheringIdUpdateRouteImport.update({ + id: "/$gatheringId/update", + path: "/$gatheringId/update", + getParentRoute: () => authGatheringsRouteRoute +} as any); const authBookingsHostingGatheringIdDashboardIndexRoute = - authBookingsHostingGatheringIdDashboardIndexRouteImport.update({ - id: '/hosting/$gatheringId/dashboard/', - path: '/hosting/$gatheringId/dashboard/', - getParentRoute: () => authBookingsRouteRoute, - } as any) + authBookingsHostingGatheringIdDashboardIndexRouteImport.update({ + id: "/hosting/$gatheringId/dashboard/", + path: "/hosting/$gatheringId/dashboard/", + getParentRoute: () => authBookingsRouteRoute + } as any); const authBookingsHostingGatheringIdDashboardScanRoute = - authBookingsHostingGatheringIdDashboardScanRouteImport.update({ - id: '/hosting/$gatheringId/dashboard/scan', - path: '/hosting/$gatheringId/dashboard/scan', - getParentRoute: () => authBookingsRouteRoute, - } as any) + authBookingsHostingGatheringIdDashboardScanRouteImport.update({ + id: "/hosting/$gatheringId/dashboard/scan", + path: "/hosting/$gatheringId/dashboard/scan", + getParentRoute: () => authBookingsRouteRoute + } as any); export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/bookings': typeof authBookingsRouteRouteWithChildren - '/gatherings': typeof GatheringsIndexRoute - '/login/callback': typeof LoginCallbackRoute - '/healthcheck': typeof HealthcheckIndexRoute - '/login': typeof LoginIndexRoute - '/gatherings/create': typeof authGatheringsCreateRoute - '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute - '/gatherings/$gatheringId/update': typeof authGatheringsGatheringIdUpdateRoute - '/bookings/attending': typeof authBookingsAttendingIndexRoute - '/bookings/hosting': typeof authBookingsHostingIndexRoute - '/bookings/hosting/$gatheringId/dashboard/scan': typeof authBookingsHostingGatheringIdDashboardScanRoute - '/bookings/hosting/$gatheringId/dashboard': typeof authBookingsHostingGatheringIdDashboardIndexRoute + "/": typeof IndexRoute; + "/bookings": typeof authBookingsRouteRouteWithChildren; + "/gatherings": typeof GatheringsIndexRoute; + "/healthcheck": typeof HealthcheckIndexRoute; + "/login": typeof LoginIndexRoute; + "/gatherings/create": typeof authGatheringsCreateRoute; + "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; + "/gatherings/$gatheringId/update": typeof authGatheringsGatheringIdUpdateRoute; + "/bookings/attending": typeof authBookingsAttendingIndexRoute; + "/bookings/hosting": typeof authBookingsHostingIndexRoute; + "/bookings/hosting/$gatheringId/dashboard/scan": typeof authBookingsHostingGatheringIdDashboardScanRoute; + "/bookings/hosting/$gatheringId/dashboard": typeof authBookingsHostingGatheringIdDashboardIndexRoute; } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/bookings': typeof authBookingsRouteRouteWithChildren - '/gatherings': typeof GatheringsIndexRoute - '/login/callback': typeof LoginCallbackRoute - '/healthcheck': typeof HealthcheckIndexRoute - '/login': typeof LoginIndexRoute - '/gatherings/create': typeof authGatheringsCreateRoute - '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute - '/gatherings/$gatheringId/update': typeof authGatheringsGatheringIdUpdateRoute - '/bookings/attending': typeof authBookingsAttendingIndexRoute - '/bookings/hosting': typeof authBookingsHostingIndexRoute - '/bookings/hosting/$gatheringId/dashboard/scan': typeof authBookingsHostingGatheringIdDashboardScanRoute - '/bookings/hosting/$gatheringId/dashboard': typeof authBookingsHostingGatheringIdDashboardIndexRoute + "/": typeof IndexRoute; + "/bookings": typeof authBookingsRouteRouteWithChildren; + "/gatherings": typeof GatheringsIndexRoute; + "/healthcheck": typeof HealthcheckIndexRoute; + "/login": typeof LoginIndexRoute; + "/gatherings/create": typeof authGatheringsCreateRoute; + "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; + "/gatherings/$gatheringId/update": typeof authGatheringsGatheringIdUpdateRoute; + "/bookings/attending": typeof authBookingsAttendingIndexRoute; + "/bookings/hosting": typeof authBookingsHostingIndexRoute; + "/bookings/hosting/$gatheringId/dashboard/scan": typeof authBookingsHostingGatheringIdDashboardScanRoute; + "/bookings/hosting/$gatheringId/dashboard": typeof authBookingsHostingGatheringIdDashboardIndexRoute; } export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/(auth)/bookings': typeof authBookingsRouteRouteWithChildren - '/(auth)/gatherings': typeof authGatheringsRouteRouteWithChildren - '/login/callback': typeof LoginCallbackRoute - '/gatherings/': typeof GatheringsIndexRoute - '/healthcheck/': typeof HealthcheckIndexRoute - '/login/': typeof LoginIndexRoute - '/(auth)/gatherings/create': typeof authGatheringsCreateRoute - '/gatherings/$gatheringId/': typeof GatheringsGatheringIdIndexRoute - '/(auth)/gatherings/$gatheringId/update': typeof authGatheringsGatheringIdUpdateRoute - '/(auth)/bookings/attending/': typeof authBookingsAttendingIndexRoute - '/(auth)/bookings/hosting/': typeof authBookingsHostingIndexRoute - '/(auth)/bookings/hosting/$gatheringId/dashboard/scan': typeof authBookingsHostingGatheringIdDashboardScanRoute - '/(auth)/bookings/hosting/$gatheringId/dashboard/': typeof authBookingsHostingGatheringIdDashboardIndexRoute + __root__: typeof rootRouteImport; + "/": typeof IndexRoute; + "/(auth)/bookings": typeof authBookingsRouteRouteWithChildren; + "/(auth)/gatherings": typeof authGatheringsRouteRouteWithChildren; + "/gatherings/": typeof GatheringsIndexRoute; + "/healthcheck/": typeof HealthcheckIndexRoute; + "/login/": typeof LoginIndexRoute; + "/(auth)/gatherings/create": typeof authGatheringsCreateRoute; + "/gatherings/$gatheringId/": typeof GatheringsGatheringIdIndexRoute; + "/(auth)/gatherings/$gatheringId/update": typeof authGatheringsGatheringIdUpdateRoute; + "/(auth)/bookings/attending/": typeof authBookingsAttendingIndexRoute; + "/(auth)/bookings/hosting/": typeof authBookingsHostingIndexRoute; + "/(auth)/bookings/hosting/$gatheringId/dashboard/scan": typeof authBookingsHostingGatheringIdDashboardScanRoute; + "/(auth)/bookings/hosting/$gatheringId/dashboard/": typeof authBookingsHostingGatheringIdDashboardIndexRoute; } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/' - | '/bookings' - | '/gatherings' - | '/login/callback' - | '/healthcheck' - | '/login' - | '/gatherings/create' - | '/gatherings/$gatheringId' - | '/gatherings/$gatheringId/update' - | '/bookings/attending' - | '/bookings/hosting' - | '/bookings/hosting/$gatheringId/dashboard/scan' - | '/bookings/hosting/$gatheringId/dashboard' - fileRoutesByTo: FileRoutesByTo - to: - | '/' - | '/bookings' - | '/gatherings' - | '/login/callback' - | '/healthcheck' - | '/login' - | '/gatherings/create' - | '/gatherings/$gatheringId' - | '/gatherings/$gatheringId/update' - | '/bookings/attending' - | '/bookings/hosting' - | '/bookings/hosting/$gatheringId/dashboard/scan' - | '/bookings/hosting/$gatheringId/dashboard' - id: - | '__root__' - | '/' - | '/(auth)/bookings' - | '/(auth)/gatherings' - | '/login/callback' - | '/gatherings/' - | '/healthcheck/' - | '/login/' - | '/(auth)/gatherings/create' - | '/gatherings/$gatheringId/' - | '/(auth)/gatherings/$gatheringId/update' - | '/(auth)/bookings/attending/' - | '/(auth)/bookings/hosting/' - | '/(auth)/bookings/hosting/$gatheringId/dashboard/scan' - | '/(auth)/bookings/hosting/$gatheringId/dashboard/' - fileRoutesById: FileRoutesById + fileRoutesByFullPath: FileRoutesByFullPath; + fullPaths: + | "/" + | "/bookings" + | "/gatherings" + | "/healthcheck" + | "/login" + | "/gatherings/create" + | "/gatherings/$gatheringId" + | "/gatherings/$gatheringId/update" + | "/bookings/attending" + | "/bookings/hosting" + | "/bookings/hosting/$gatheringId/dashboard/scan" + | "/bookings/hosting/$gatheringId/dashboard"; + fileRoutesByTo: FileRoutesByTo; + to: + | "/" + | "/bookings" + | "/gatherings" + | "/healthcheck" + | "/login" + | "/gatherings/create" + | "/gatherings/$gatheringId" + | "/gatherings/$gatheringId/update" + | "/bookings/attending" + | "/bookings/hosting" + | "/bookings/hosting/$gatheringId/dashboard/scan" + | "/bookings/hosting/$gatheringId/dashboard"; + id: + | "__root__" + | "/" + | "/(auth)/bookings" + | "/(auth)/gatherings" + | "/gatherings/" + | "/healthcheck/" + | "/login/" + | "/(auth)/gatherings/create" + | "/gatherings/$gatheringId/" + | "/(auth)/gatherings/$gatheringId/update" + | "/(auth)/bookings/attending/" + | "/(auth)/bookings/hosting/" + | "/(auth)/bookings/hosting/$gatheringId/dashboard/scan" + | "/(auth)/bookings/hosting/$gatheringId/dashboard/"; + fileRoutesById: FileRoutesById; } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - authBookingsRouteRoute: typeof authBookingsRouteRouteWithChildren - authGatheringsRouteRoute: typeof authGatheringsRouteRouteWithChildren - LoginCallbackRoute: typeof LoginCallbackRoute - GatheringsIndexRoute: typeof GatheringsIndexRoute - HealthcheckIndexRoute: typeof HealthcheckIndexRoute - LoginIndexRoute: typeof LoginIndexRoute - GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute + IndexRoute: typeof IndexRoute; + authBookingsRouteRoute: typeof authBookingsRouteRouteWithChildren; + authGatheringsRouteRoute: typeof authGatheringsRouteRouteWithChildren; + GatheringsIndexRoute: typeof GatheringsIndexRoute; + HealthcheckIndexRoute: typeof HealthcheckIndexRoute; + LoginIndexRoute: typeof LoginIndexRoute; + GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute; } -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - '/login/': { - id: '/login/' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof LoginIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/healthcheck/': { - id: '/healthcheck/' - path: '/healthcheck' - fullPath: '/healthcheck' - preLoaderRoute: typeof HealthcheckIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/gatherings/': { - id: '/gatherings/' - path: '/gatherings' - fullPath: '/gatherings' - preLoaderRoute: typeof GatheringsIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/login/callback': { - id: '/login/callback' - path: '/login/callback' - fullPath: '/login/callback' - preLoaderRoute: typeof LoginCallbackRouteImport - parentRoute: typeof rootRouteImport - } - '/(auth)/gatherings': { - id: '/(auth)/gatherings' - path: '/gatherings' - fullPath: '/gatherings' - preLoaderRoute: typeof authGatheringsRouteRouteImport - parentRoute: typeof rootRouteImport - } - '/(auth)/bookings': { - id: '/(auth)/bookings' - path: '/bookings' - fullPath: '/bookings' - preLoaderRoute: typeof authBookingsRouteRouteImport - parentRoute: typeof rootRouteImport - } - '/gatherings/$gatheringId/': { - id: '/gatherings/$gatheringId/' - path: '/gatherings/$gatheringId' - fullPath: '/gatherings/$gatheringId' - preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport - parentRoute: typeof rootRouteImport - } - '/(auth)/gatherings/create': { - id: '/(auth)/gatherings/create' - path: '/create' - fullPath: '/gatherings/create' - preLoaderRoute: typeof authGatheringsCreateRouteImport - parentRoute: typeof authGatheringsRouteRoute - } - '/(auth)/bookings/hosting/': { - id: '/(auth)/bookings/hosting/' - path: '/hosting' - fullPath: '/bookings/hosting' - preLoaderRoute: typeof authBookingsHostingIndexRouteImport - parentRoute: typeof authBookingsRouteRoute - } - '/(auth)/bookings/attending/': { - id: '/(auth)/bookings/attending/' - path: '/attending' - fullPath: '/bookings/attending' - preLoaderRoute: typeof authBookingsAttendingIndexRouteImport - parentRoute: typeof authBookingsRouteRoute - } - '/(auth)/gatherings/$gatheringId/update': { - id: '/(auth)/gatherings/$gatheringId/update' - path: '/$gatheringId/update' - fullPath: '/gatherings/$gatheringId/update' - preLoaderRoute: typeof authGatheringsGatheringIdUpdateRouteImport - parentRoute: typeof authGatheringsRouteRoute - } - '/(auth)/bookings/hosting/$gatheringId/dashboard/': { - id: '/(auth)/bookings/hosting/$gatheringId/dashboard/' - path: '/hosting/$gatheringId/dashboard' - fullPath: '/bookings/hosting/$gatheringId/dashboard' - preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardIndexRouteImport - parentRoute: typeof authBookingsRouteRoute - } - '/(auth)/bookings/hosting/$gatheringId/dashboard/scan': { - id: '/(auth)/bookings/hosting/$gatheringId/dashboard/scan' - path: '/hosting/$gatheringId/dashboard/scan' - fullPath: '/bookings/hosting/$gatheringId/dashboard/scan' - preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardScanRouteImport - parentRoute: typeof authBookingsRouteRoute - } - } +declare module "@tanstack/react-router" { + interface FileRoutesByPath { + "/": { + id: "/"; + path: "/"; + fullPath: "/"; + preLoaderRoute: typeof IndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/login/": { + id: "/login/"; + path: "/login"; + fullPath: "/login"; + preLoaderRoute: typeof LoginIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/healthcheck/": { + id: "/healthcheck/"; + path: "/healthcheck"; + fullPath: "/healthcheck"; + preLoaderRoute: typeof HealthcheckIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/gatherings/": { + id: "/gatherings/"; + path: "/gatherings"; + fullPath: "/gatherings"; + preLoaderRoute: typeof GatheringsIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/(auth)/gatherings": { + id: "/(auth)/gatherings"; + path: "/gatherings"; + fullPath: "/gatherings"; + preLoaderRoute: typeof authGatheringsRouteRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/(auth)/bookings": { + id: "/(auth)/bookings"; + path: "/bookings"; + fullPath: "/bookings"; + preLoaderRoute: typeof authBookingsRouteRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/gatherings/$gatheringId/": { + id: "/gatherings/$gatheringId/"; + path: "/gatherings/$gatheringId"; + fullPath: "/gatherings/$gatheringId"; + preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport; + parentRoute: typeof rootRouteImport; + }; + "/(auth)/gatherings/create": { + id: "/(auth)/gatherings/create"; + path: "/create"; + fullPath: "/gatherings/create"; + preLoaderRoute: typeof authGatheringsCreateRouteImport; + parentRoute: typeof authGatheringsRouteRoute; + }; + "/(auth)/bookings/hosting/": { + id: "/(auth)/bookings/hosting/"; + path: "/hosting"; + fullPath: "/bookings/hosting"; + preLoaderRoute: typeof authBookingsHostingIndexRouteImport; + parentRoute: typeof authBookingsRouteRoute; + }; + "/(auth)/bookings/attending/": { + id: "/(auth)/bookings/attending/"; + path: "/attending"; + fullPath: "/bookings/attending"; + preLoaderRoute: typeof authBookingsAttendingIndexRouteImport; + parentRoute: typeof authBookingsRouteRoute; + }; + "/(auth)/gatherings/$gatheringId/update": { + id: "/(auth)/gatherings/$gatheringId/update"; + path: "/$gatheringId/update"; + fullPath: "/gatherings/$gatheringId/update"; + preLoaderRoute: typeof authGatheringsGatheringIdUpdateRouteImport; + parentRoute: typeof authGatheringsRouteRoute; + }; + "/(auth)/bookings/hosting/$gatheringId/dashboard/": { + id: "/(auth)/bookings/hosting/$gatheringId/dashboard/"; + path: "/hosting/$gatheringId/dashboard"; + fullPath: "/bookings/hosting/$gatheringId/dashboard"; + preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardIndexRouteImport; + parentRoute: typeof authBookingsRouteRoute; + }; + "/(auth)/bookings/hosting/$gatheringId/dashboard/scan": { + id: "/(auth)/bookings/hosting/$gatheringId/dashboard/scan"; + path: "/hosting/$gatheringId/dashboard/scan"; + fullPath: "/bookings/hosting/$gatheringId/dashboard/scan"; + preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardScanRouteImport; + parentRoute: typeof authBookingsRouteRoute; + }; + } } interface authBookingsRouteRouteChildren { - authBookingsAttendingIndexRoute: typeof authBookingsAttendingIndexRoute - authBookingsHostingIndexRoute: typeof authBookingsHostingIndexRoute - authBookingsHostingGatheringIdDashboardScanRoute: typeof authBookingsHostingGatheringIdDashboardScanRoute - authBookingsHostingGatheringIdDashboardIndexRoute: typeof authBookingsHostingGatheringIdDashboardIndexRoute + authBookingsAttendingIndexRoute: typeof authBookingsAttendingIndexRoute; + authBookingsHostingIndexRoute: typeof authBookingsHostingIndexRoute; + authBookingsHostingGatheringIdDashboardScanRoute: typeof authBookingsHostingGatheringIdDashboardScanRoute; + authBookingsHostingGatheringIdDashboardIndexRoute: typeof authBookingsHostingGatheringIdDashboardIndexRoute; } const authBookingsRouteRouteChildren: authBookingsRouteRouteChildren = { - authBookingsAttendingIndexRoute: authBookingsAttendingIndexRoute, - authBookingsHostingIndexRoute: authBookingsHostingIndexRoute, - authBookingsHostingGatheringIdDashboardScanRoute: - authBookingsHostingGatheringIdDashboardScanRoute, - authBookingsHostingGatheringIdDashboardIndexRoute: - authBookingsHostingGatheringIdDashboardIndexRoute, -} + authBookingsAttendingIndexRoute: authBookingsAttendingIndexRoute, + authBookingsHostingIndexRoute: authBookingsHostingIndexRoute, + authBookingsHostingGatheringIdDashboardScanRoute: + authBookingsHostingGatheringIdDashboardScanRoute, + authBookingsHostingGatheringIdDashboardIndexRoute: + authBookingsHostingGatheringIdDashboardIndexRoute +}; -const authBookingsRouteRouteWithChildren = - authBookingsRouteRoute._addFileChildren(authBookingsRouteRouteChildren) +const authBookingsRouteRouteWithChildren = authBookingsRouteRoute._addFileChildren( + authBookingsRouteRouteChildren +); interface authGatheringsRouteRouteChildren { - authGatheringsCreateRoute: typeof authGatheringsCreateRoute - authGatheringsGatheringIdUpdateRoute: typeof authGatheringsGatheringIdUpdateRoute + authGatheringsCreateRoute: typeof authGatheringsCreateRoute; + authGatheringsGatheringIdUpdateRoute: typeof authGatheringsGatheringIdUpdateRoute; } const authGatheringsRouteRouteChildren: authGatheringsRouteRouteChildren = { - authGatheringsCreateRoute: authGatheringsCreateRoute, - authGatheringsGatheringIdUpdateRoute: authGatheringsGatheringIdUpdateRoute, -} + authGatheringsCreateRoute: authGatheringsCreateRoute, + authGatheringsGatheringIdUpdateRoute: authGatheringsGatheringIdUpdateRoute +}; -const authGatheringsRouteRouteWithChildren = - authGatheringsRouteRoute._addFileChildren(authGatheringsRouteRouteChildren) +const authGatheringsRouteRouteWithChildren = authGatheringsRouteRoute._addFileChildren( + authGatheringsRouteRouteChildren +); const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - authBookingsRouteRoute: authBookingsRouteRouteWithChildren, - authGatheringsRouteRoute: authGatheringsRouteRouteWithChildren, - LoginCallbackRoute: LoginCallbackRoute, - GatheringsIndexRoute: GatheringsIndexRoute, - HealthcheckIndexRoute: HealthcheckIndexRoute, - LoginIndexRoute: LoginIndexRoute, - GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute, -} + IndexRoute: IndexRoute, + authBookingsRouteRoute: authBookingsRouteRouteWithChildren, + authGatheringsRouteRoute: authGatheringsRouteRouteWithChildren, + GatheringsIndexRoute: GatheringsIndexRoute, + HealthcheckIndexRoute: HealthcheckIndexRoute, + LoginIndexRoute: LoginIndexRoute, + GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute +}; export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes() + ._addFileChildren(rootRouteChildren) + ._addFileTypes(); diff --git a/src/evently.client/src/routes/(auth)/bookings/hosting/$gatheringId/dashboard.index.tsx b/src/evently.client/src/routes/(auth)/bookings/hosting/$gatheringId/dashboard.index.tsx index b8fdddc..d8fcb4e 100644 --- a/src/evently.client/src/routes/(auth)/bookings/hosting/$gatheringId/dashboard.index.tsx +++ b/src/evently.client/src/routes/(auth)/bookings/hosting/$gatheringId/dashboard.index.tsx @@ -7,10 +7,10 @@ import cloneDeep from "lodash.clonedeep"; import { Icon } from "@iconify/react"; import { json2csv } from "json-2-csv"; import { downloadFile } from "~/lib/services"; -import type { PageResult } from "~/lib/domains/models"; import { useQuery } from "@tanstack/react-query"; import { BookingsTable, Jumbotron, StatsCard } from "./-components"; import { useInterval } from "usehooks-ts"; +import type { PageResult } from "~/lib/domains/interfaces"; export const Route = createFileRoute("/(auth)/bookings/hosting/$gatheringId/dashboard/")({ loader: async ({ params }) => { diff --git a/src/evently.client/src/routes/__root.tsx b/src/evently.client/src/routes/__root.tsx index a8c887d..1d2ab72 100644 --- a/src/evently.client/src/routes/__root.tsx +++ b/src/evently.client/src/routes/__root.tsx @@ -4,11 +4,7 @@ import { Navbar } from "~/lib/components"; import { type JSX } from "react"; import { getAccount } from "~/lib/services"; import { Account } from "~/lib/domains/entities"; - -interface RouteContext { - // The ReturnType of your useAuth hook or the value of your AuthContext - account: Account; -} +import type { RouteContext } from "~/lib/domains/interfaces/route-context.ts"; export const Route = createRootRouteWithContext()({ beforeLoad: async () => { @@ -22,7 +18,7 @@ export function App(): JSX.Element { return (
    -
    +
    diff --git a/src/evently.client/src/routes/gatherings/-index.test.tsx b/src/evently.client/src/routes/gatherings/-index.test.tsx new file mode 100644 index 0000000..021b44c --- /dev/null +++ b/src/evently.client/src/routes/gatherings/-index.test.tsx @@ -0,0 +1,37 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { getMockGatherings } from "~/lib/services/gathering-service.mock"; +import * as GatheringService from "~/lib/services"; +import userEvent from "@testing-library/user-event"; +import { TestWrapper, WrapperDataTestId } from "~/lib/components"; +import { GatheringsPage } from "./index.tsx"; +import type { GetGatheringsParams } from "~/lib/services"; + +it("renders GatheringPage", async () => { + const spy = vi.spyOn(GatheringService, "getGatherings"); + spy.mockImplementation(async (params: GetGatheringsParams) => await getMockGatherings(params)); + + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + + expect(spy).toHaveBeenCalledTimes(1); + let element = await screen.findByText("Tech Conference 2024"); + expect(element).toBeInTheDocument(); + + element = await screen.findByText("Design Workshop"); + expect(element).toBeInTheDocument(); + + element = await screen.findByText("Networking Event"); + expect(element).toBeInTheDocument(); + + const input: HTMLInputElement = screen.getByPlaceholderText("Search Gatherings"); + await userEvent.type(input, "T"); + expect(spy).toHaveBeenCalledTimes(2); + + const button: HTMLButtonElement = screen.getByRole("button", { name: "»" }); + await userEvent.click(button); + expect(spy).toHaveBeenCalledTimes(3); +}); diff --git a/src/evently.client/src/routes/gatherings/index.tsx b/src/evently.client/src/routes/gatherings/index.tsx index 85d2bd9..63e176c 100644 --- a/src/evently.client/src/routes/gatherings/index.tsx +++ b/src/evently.client/src/routes/gatherings/index.tsx @@ -4,8 +4,8 @@ import { useQuery } from "@tanstack/react-query"; import { Gathering } from "~/lib/domains/entities"; import { getGatherings, type GetGatheringsParams } from "~/lib/services"; import { Card } from "~/lib/components"; -import type { PageResult } from "~/lib/domains/models"; import { Icon } from "@iconify/react"; +import type { PageResult } from "~/lib/domains/interfaces"; export const Route = createFileRoute("/gatherings/")({ component: GatheringsPage, diff --git a/src/evently.client/src/routes/healthcheck/-index.test.tsx b/src/evently.client/src/routes/healthcheck/-index.test.tsx new file mode 100644 index 0000000..a5a6540 --- /dev/null +++ b/src/evently.client/src/routes/healthcheck/-index.test.tsx @@ -0,0 +1,55 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import * as healthCheckService from "./-services/health-check-service"; +import { HealthcheckPage } from "./index.tsx"; +import { TestWrapper, WrapperDataTestId } from "~/lib/components"; + +// Mock the health check service +vi.mock("./-services/health-check-service", () => ({ + getStatus: vi.fn() +})); + +const mockGetStatus = vi.mocked(healthCheckService.getStatus); + +describe("HealthcheckPage", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should render loading state initially", async () => { + mockGetStatus.mockReturnValue(new Promise(() => {})); // Never resolving with a promise + render( + + + + ); + await waitFor(() => screen.findByTestId(WrapperDataTestId)); + + expect(screen.getByText("Loading...")).toBeInTheDocument(); + }); + + it("should render health statuses when data is loaded successfully", async () => { + const mockStatuses = { + Database: "Healthy", + API: "Healthy" + }; + + mockGetStatus.mockResolvedValue(mockStatuses); + + render( + + + + ); + await screen.findByTestId("root-layout"); + + // Check that all status entries are rendered + // await router.navigate({ to: "/healthcheck" }); + expect(screen.getByText("Database: Healthy")).toBeInTheDocument(); + expect(screen.getByText("API: Healthy")).toBeInTheDocument(); + }); +}); diff --git a/src/evently.client/src/routes/healthcheck/index.tsx b/src/evently.client/src/routes/healthcheck/index.tsx index ae5deea..6c465f6 100644 --- a/src/evently.client/src/routes/healthcheck/index.tsx +++ b/src/evently.client/src/routes/healthcheck/index.tsx @@ -15,15 +15,15 @@ export function HealthcheckPage(): JSX.Element { const statuses: Record = _statuses ?? {}; return ( -
    +
    {isLoading ? (

    Loading...

    ) : ( <> {Object.entries(statuses).map(([key, value]) => ( -

    + {key}: {value} -

    + ))} )} diff --git a/src/evently.client/src/routes/login/callback.tsx b/src/evently.client/src/routes/login/callback.tsx deleted file mode 100644 index c2b27de..0000000 --- a/src/evently.client/src/routes/login/callback.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { useEffect } from "react"; -import { getAccount } from "~/lib/services"; - -export const Route = createFileRoute("/login/callback")({ - component: LoginCallbackPage, - beforeLoad: async () => { - const account = await getAccount(); - return { account }; - } -}); -export function LoginCallbackPage() { - const navigate = useNavigate(); - useEffect(() => { - navigate({ to: "/bookings/attending" }).then(); - }, [navigate]); -} diff --git a/src/evently.client/src/routes/login/index.tsx b/src/evently.client/src/routes/login/index.tsx index a879d78..002592a 100644 --- a/src/evently.client/src/routes/login/index.tsx +++ b/src/evently.client/src/routes/login/index.tsx @@ -7,7 +7,7 @@ export const Route = createFileRoute("/login/")({ }); export function LoginPage() { - const defaultRedirect: URL = new URL("/login/callback", window.location.href); + const defaultRedirect: URL = new URL("/bookings/attending", window.location.href); const searchParams: Record = useSearch({ strict: false }); const redirect: string = searchParams["redirect"] ?? defaultRedirect.href; console.log({ redirect }); diff --git a/src/evently.client/src/setup-tests.ts b/src/evently.client/src/setup-tests.ts new file mode 100644 index 0000000..f149f27 --- /dev/null +++ b/src/evently.client/src/setup-tests.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/src/evently.client/src/vite-env.d.ts b/src/evently.client/src/vite-env.d.ts index c8cbea0..dc2737f 100644 --- a/src/evently.client/src/vite-env.d.ts +++ b/src/evently.client/src/vite-env.d.ts @@ -1,2 +1,2 @@ /// -// Register the router instance for type safety +/// diff --git a/src/evently.client/tsconfig.app.json b/src/evently.client/tsconfig.app.json index 4b53749..6ff0d73 100644 --- a/src/evently.client/tsconfig.app.json +++ b/src/evently.client/tsconfig.app.json @@ -6,6 +6,7 @@ "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, + "types": ["vitest/globals", "@testing-library/jest-dom"], /* Bundler mode */ "moduleResolution": "bundler", diff --git a/src/evently.client/vite.config.ts b/src/evently.client/vite.config.ts index 0d20874..9b745d9 100644 --- a/src/evently.client/vite.config.ts +++ b/src/evently.client/vite.config.ts @@ -1,10 +1,10 @@ -import { defineConfig } from "vite"; import { env } from "process"; import { generatePem, getBackendUrl, type KeyCertPair } from "./aspnetcore-https.ts"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { tanstackRouter } from "@tanstack/router-plugin/vite"; import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "vitest/config"; const keyCert: KeyCertPair = generatePem(); const { key, cert } = keyCert; @@ -13,6 +13,12 @@ console.log({ backendUrl }); // https://vitejs.dev/config/ export default defineConfig({ + test: { + include: ["src/**/*.{test,spec}.{js,ts,jsx,tsx}"], + environment: "jsdom", + setupFiles: "src/setup-tests.ts", + globals: true + }, plugins: [ tailwindcss(), // Please make sure that '@tanstack/router-plugin' is passed before '@vitejs/plugin-react'