From c56924d95cbbc31721445ff7bba8df3c328f7487 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 00:20:41 +0000 Subject: [PATCH] feat(html): add cloneable context data helpers Co-Authored-By: Virgil --- context.go | 28 ++++++++++++++++++++++++++++ context_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/context.go b/context.go index d6fa55c..e2902dc 100644 --- a/context.go +++ b/context.go @@ -21,6 +21,23 @@ type Context struct { service Translator } +// Clone returns a shallow copy of the context with an independent Data map. +// Example: next := ctx.Clone().SetData("theme", "dark"). +func (ctx *Context) Clone() *Context { + if ctx == nil { + return nil + } + + clone := *ctx + if ctx.Data != nil { + clone.Data = make(map[string]any, len(ctx.Data)) + for key, value := range ctx.Data { + clone.Data[key] = value + } + } + return &clone +} + func applyLocaleToService(svc Translator, locale string) { if svc == nil || locale == "" { return @@ -127,6 +144,17 @@ func (ctx *Context) SetData(key string, value any) *Context { return ctx } +// WithData returns a cloned context with one additional data value set. +// Example: next := ctx.WithData("theme", "dark"). +func (ctx *Context) WithData(key string, value any) *Context { + clone := ctx.Clone() + if clone == nil { + return nil + } + clone.SetData(key, value) + return clone +} + // SetLocale updates the context locale and reapplies it to the active // translator. // Example: ctx.SetLocale("en-US"). diff --git a/context_test.go b/context_test.go index c864f40..a8776b9 100644 --- a/context_test.go +++ b/context_test.go @@ -134,6 +134,53 @@ func TestContext_SetData_InitialisesNilMap(t *testing.T) { } } +func TestContext_CloneCopiesDataWithoutSharingMap(t *testing.T) { + svc := &localeTranslator{} + ctx := NewContextWithService(svc, "en-GB") + ctx.SetIdentity("user-123") + ctx.SetData("theme", "dark") + + clone := ctx.Clone() + + if clone == ctx { + t.Fatal("Clone should return a distinct context instance") + } + if clone.service != ctx.service { + t.Fatal("Clone should preserve the active translator") + } + if clone.Locale != ctx.Locale { + t.Fatalf("Clone should preserve locale, got %q want %q", clone.Locale, ctx.Locale) + } + if clone.Identity != ctx.Identity { + t.Fatalf("Clone should preserve identity, got %q want %q", clone.Identity, ctx.Identity) + } + + clone.SetData("theme", "light") + if got := ctx.Data["theme"]; got != "dark" { + t.Fatalf("Clone should not share Data map with original, got %v", got) + } +} + +func TestContext_WithDataReturnsClonedContext(t *testing.T) { + ctx := NewContext() + ctx.SetData("theme", "dark") + + next := ctx.WithData("locale", "fr-FR") + + if next == ctx { + t.Fatal("WithData should return a cloned context") + } + if got := ctx.Data["locale"]; got != nil { + t.Fatalf("WithData should not mutate the original context, got %v", got) + } + if got := next.Data["locale"]; got != "fr-FR" { + t.Fatalf("WithData should set the requested value on the clone, got %v", got) + } + if got := next.Data["theme"]; got != "dark" { + t.Fatalf("WithData should preserve existing data on the clone, got %v", got) + } +} + func TestContext_Setters_NilReceiver(t *testing.T) { var ctx *Context