package world import ( "image" "image/color" "testing" "github.com/stretchr/testify/require" ) type cacheTheme struct{} func (cacheTheme) ID() string { return "cache" } func (cacheTheme) Name() string { return "cache" } func (cacheTheme) BackgroundColor() color.Color { return color.RGBA{A: 255} } func (cacheTheme) BackgroundImage() image.Image { return nil } func (cacheTheme) BackgroundTileMode() BackgroundTileMode { return BackgroundTileNone } func (cacheTheme) BackgroundScaleMode() BackgroundScaleMode { return BackgroundScaleNone } func (cacheTheme) BackgroundAnchorMode() BackgroundAnchorMode { return BackgroundAnchorWorld } func (cacheTheme) PointStyle() Style { return Style{FillColor: color.RGBA{A: 255}, PointRadiusPx: 2} } func (cacheTheme) LineStyle() Style { return Style{StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 1} } func (cacheTheme) CircleStyle() Style { return Style{FillColor: color.RGBA{A: 255}, StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 1} } func (cacheTheme) PointClassOverride(PointClassID) (StyleOverride, bool) { return StyleOverride{}, false } func (cacheTheme) LineClassOverride(LineClassID) (StyleOverride, bool) { return StyleOverride{}, false } func (cacheTheme) CircleClassOverride(CircleClassID) (StyleOverride, bool) { return StyleOverride{}, false } type cacheTheme2 struct{ cacheTheme } func (cacheTheme2) ID() string { return "cache2" } func (cacheTheme2) Name() string { return "cache2" } func (cacheTheme2) CircleStyle() Style { return Style{FillColor: color.RGBA{B: 200, A: 255}, StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 3} } func (cacheTheme2) PointClassOverride(PointClassID) (StyleOverride, bool) { return StyleOverride{}, false } func (cacheTheme2) LineClassOverride(LineClassID) (StyleOverride, bool) { return StyleOverride{}, false } func (cacheTheme2) CircleClassOverride(CircleClassID) (StyleOverride, bool) { return StyleOverride{}, false } func TestDerivedStyleCache_ReusesDerivedStylesAcrossObjectsAndThemes(t *testing.T) { t.Parallel() w := NewWorld(10, 10) w.SetTheme(cacheTheme{}) before := w.styles.Count() white := color.RGBA{R: 255, G: 255, B: 255, A: 255} ov := StyleOverride{StrokeColor: white} id1, err := w.AddCircle(5, 5, 2, CircleWithStyleOverride(ov)) require.NoError(t, err) id2, err := w.AddCircle(6, 5, 2, CircleWithStyleOverride(ov)) require.NoError(t, err) c1 := w.objects[id1].(Circle) c2 := w.objects[id2].(Circle) require.Equal(t, c1.StyleID, c2.StyleID, "same override must reuse derived style ID") afterAdd := w.styles.Count() require.Equal(t, before+1, afterAdd, "only one derived style should be added for identical overrides") // Change theme: derived cache is cleared and new base IDs are created; override must still be applied, // and both objects should again share one derived style for the new base. w.SetTheme(cacheTheme2{}) c1b := w.objects[id1].(Circle) c2b := w.objects[id2].(Circle) require.Equal(t, c1b.StyleID, c2b.StyleID) afterTheme := w.styles.Count() // Theme change creates 3 new theme default styles + 1 new derived for the override. require.GreaterOrEqual(t, afterTheme, afterAdd+4) }