@tldraw/editor 3.14.0-canary.e0ab6f4c80f9 → 3.14.0-canary.e95fdc82a46a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +75 -70
- package/dist-cjs/index.js +10 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +44 -73
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js +22 -22
- package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -20
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js +16 -16
- package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
- package/dist-cjs/lib/editor/managers/{ClickManager.js → ClickManager/ClickManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{EdgeScrollManager.js → EdgeScrollManager/EdgeScrollManager.js} +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{FocusManager.js → FocusManager/FocusManager.js} +2 -0
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{FontManager.js → FontManager/FontManager.js} +5 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{HistoryManager.js → HistoryManager/HistoryManager.js} +64 -6
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{ScribbleManager.js → ScribbleManager/ScribbleManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{TickManager.js → TickManager/TickManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/{UserPreferencesManager.js → UserPreferencesManager/UserPreferencesManager.js} +1 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +7 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +8 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +6 -0
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js.map +1 -1
- package/dist-cjs/lib/primitives/Box.js +39 -33
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +18 -13
- package/dist-cjs/lib/primitives/Vec.js.map +3 -3
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -21
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +5 -0
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
- package/dist-cjs/lib/utils/reorderShapes.js +11 -10
- package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
- package/dist-cjs/lib/utils/richText.js.map +1 -1
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +75 -70
- package/dist-esm/index.mjs +17 -9
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +44 -73
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +22 -22
- package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -20
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +16 -16
- package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/{ClickManager.mjs → ClickManager/ClickManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{EdgeScrollManager.mjs → EdgeScrollManager/EdgeScrollManager.mjs} +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{FocusManager.mjs → FocusManager/FocusManager.mjs} +2 -0
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{FontManager.mjs → FontManager/FontManager.mjs} +5 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{HistoryManager.mjs → HistoryManager/HistoryManager.mjs} +60 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{ScribbleManager.mjs → ScribbleManager/ScribbleManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{TickManager.mjs → TickManager/TickManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/{UserPreferencesManager.mjs → UserPreferencesManager/UserPreferencesManager.mjs} +1 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +7 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +8 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +6 -0
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +1 -1
- package/dist-esm/lib/primitives/Box.mjs +39 -33
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +19 -14
- package/dist-esm/lib/primitives/Vec.mjs.map +3 -3
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -21
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +7 -1
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
- package/dist-esm/lib/utils/reorderShapes.mjs +11 -10
- package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
- package/dist-esm/lib/utils/richText.mjs.map +1 -1
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +18 -8
- package/src/lib/editor/Editor.test.ts +252 -3
- package/src/lib/editor/Editor.ts +47 -75
- package/src/lib/editor/derivations/bindingsIndex.ts +27 -26
- package/src/lib/editor/derivations/notVisibleShapes.ts +24 -25
- package/src/lib/editor/derivations/parentsToChildren.ts +28 -25
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +442 -0
- package/src/lib/editor/managers/{ClickManager.ts → ClickManager/ClickManager.ts} +3 -3
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +374 -0
- package/src/lib/editor/managers/{EdgeScrollManager.ts → EdgeScrollManager/EdgeScrollManager.ts} +3 -3
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +455 -0
- package/src/lib/editor/managers/{FocusManager.ts → FocusManager/FocusManager.ts} +3 -1
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +263 -0
- package/src/lib/editor/managers/{FontManager.ts → FontManager/FontManager.ts} +6 -2
- package/src/lib/editor/managers/{HistoryManager.test.ts → HistoryManager/HistoryManager.test.ts} +388 -1
- package/src/lib/editor/managers/{HistoryManager.ts → HistoryManager/HistoryManager.ts} +73 -2
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +624 -0
- package/src/lib/editor/managers/{ScribbleManager.ts → ScribbleManager/ScribbleManager.ts} +2 -2
- package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +485 -0
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +411 -0
- package/src/lib/editor/managers/{TextManager.ts → TextManager/TextManager.ts} +1 -1
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +314 -0
- package/src/lib/editor/managers/{TickManager.ts → TickManager/TickManager.ts} +2 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +591 -0
- package/src/lib/editor/managers/{UserPreferencesManager.ts → UserPreferencesManager/UserPreferencesManager.ts} +2 -2
- package/src/lib/editor/shapes/ShapeUtil.ts +10 -1
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +8 -0
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +5 -2
- package/src/lib/exports/getSvgJsx.tsx +1 -1
- package/src/lib/primitives/Box.test.ts +588 -7
- package/src/lib/primitives/Box.ts +41 -33
- package/src/lib/primitives/Vec.test.ts +2 -2
- package/src/lib/primitives/Vec.ts +15 -10
- package/src/lib/primitives/geometry/Arc2d.ts +42 -23
- package/src/lib/primitives/geometry/Circle2d.ts +12 -12
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
- package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
- package/src/lib/primitives/geometry/Edge2d.ts +14 -25
- package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
- package/src/lib/primitives/geometry/Geometry2d.ts +6 -0
- package/src/lib/primitives/geometry/Point2d.ts +6 -6
- package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
- package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
- package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
- package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
- package/src/lib/utils/areShapesContentEqual.ts +2 -1
- package/src/lib/utils/reorderShapes.ts +10 -13
- package/src/lib/utils/richText.ts +1 -1
- package/src/version.ts +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/EdgeScrollManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/FocusManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/HistoryManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/ScribbleManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/Stack.js +0 -82
- package/dist-cjs/lib/editor/managers/Stack.js.map +0 -7
- package/dist-cjs/lib/editor/managers/TextManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/TickManager.js.map +0 -7
- package/dist-cjs/lib/editor/managers/UserPreferencesManager.js.map +0 -7
- package/dist-esm/lib/editor/managers/ClickManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/EdgeScrollManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/FocusManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/HistoryManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/ScribbleManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/Stack.mjs +0 -62
- package/dist-esm/lib/editor/managers/Stack.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/TickManager.mjs.map +0 -7
- package/dist-esm/lib/editor/managers/UserPreferencesManager.mjs.map +0 -7
- package/src/lib/editor/managers/ScribbleManager.test.ts +0 -32
- package/src/lib/editor/managers/Stack.ts +0 -71
- /package/dist-cjs/lib/editor/managers/{TextManager.js → TextManager/TextManager.js} +0 -0
- /package/dist-esm/lib/editor/managers/{TextManager.mjs → TextManager/TextManager.mjs} +0 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { atom } from '@tldraw/state'
|
|
2
|
+
import { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'
|
|
3
|
+
import { TLUser } from '../../../config/createTLUser'
|
|
4
|
+
import { UserPreferencesManager } from './UserPreferencesManager'
|
|
5
|
+
|
|
6
|
+
// Mock window.matchMedia
|
|
7
|
+
const mockMatchMedia = jest.fn()
|
|
8
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
9
|
+
writable: true,
|
|
10
|
+
value: mockMatchMedia,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
describe('UserPreferencesManager', () => {
|
|
14
|
+
let mockUser: jest.Mocked<TLUser>
|
|
15
|
+
let mockUserPreferences: TLUserPreferences
|
|
16
|
+
let userPreferencesAtom: any
|
|
17
|
+
let userPreferencesManager: UserPreferencesManager
|
|
18
|
+
|
|
19
|
+
const createMockUserPreferences = (
|
|
20
|
+
overrides: Partial<TLUserPreferences> = {}
|
|
21
|
+
): TLUserPreferences => ({
|
|
22
|
+
id: 'test-user-id',
|
|
23
|
+
name: 'Test User',
|
|
24
|
+
color: '#FF802B',
|
|
25
|
+
locale: 'en',
|
|
26
|
+
animationSpeed: 1,
|
|
27
|
+
edgeScrollSpeed: 1,
|
|
28
|
+
colorScheme: 'light',
|
|
29
|
+
isSnapMode: false,
|
|
30
|
+
isWrapMode: false,
|
|
31
|
+
isDynamicSizeMode: false,
|
|
32
|
+
isPasteAtCursorMode: false,
|
|
33
|
+
...overrides,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
jest.clearAllMocks()
|
|
38
|
+
|
|
39
|
+
mockUserPreferences = createMockUserPreferences()
|
|
40
|
+
userPreferencesAtom = atom('userPreferences', mockUserPreferences)
|
|
41
|
+
|
|
42
|
+
mockUser = {
|
|
43
|
+
userPreferences: userPreferencesAtom,
|
|
44
|
+
setUserPreferences: jest.fn((prefs) => {
|
|
45
|
+
userPreferencesAtom.set(prefs)
|
|
46
|
+
}),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Default matchMedia mock - no dark mode preference
|
|
50
|
+
mockMatchMedia.mockReturnValue({
|
|
51
|
+
matches: false,
|
|
52
|
+
addEventListener: jest.fn(),
|
|
53
|
+
removeEventListener: jest.fn(),
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('constructor', () => {
|
|
58
|
+
it('should initialize with light system color scheme when matchMedia not available', () => {
|
|
59
|
+
// Test when window.matchMedia is not available
|
|
60
|
+
delete (window as any).matchMedia
|
|
61
|
+
|
|
62
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
63
|
+
|
|
64
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
|
|
65
|
+
|
|
66
|
+
// Restore matchMedia
|
|
67
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
68
|
+
writable: true,
|
|
69
|
+
value: mockMatchMedia,
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should initialize with light system color scheme when dark mode not preferred', () => {
|
|
74
|
+
mockMatchMedia.mockReturnValue({
|
|
75
|
+
matches: false,
|
|
76
|
+
addEventListener: jest.fn(),
|
|
77
|
+
removeEventListener: jest.fn(),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
81
|
+
|
|
82
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should initialize with dark system color scheme when dark mode preferred', () => {
|
|
86
|
+
mockMatchMedia.mockReturnValue({
|
|
87
|
+
matches: true,
|
|
88
|
+
addEventListener: jest.fn(),
|
|
89
|
+
removeEventListener: jest.fn(),
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
93
|
+
|
|
94
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('dark')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should set up media query listener for color scheme changes', () => {
|
|
98
|
+
const mockAddEventListener = jest.fn()
|
|
99
|
+
const mockRemoveEventListener = jest.fn()
|
|
100
|
+
|
|
101
|
+
mockMatchMedia.mockReturnValue({
|
|
102
|
+
matches: false,
|
|
103
|
+
addEventListener: mockAddEventListener,
|
|
104
|
+
removeEventListener: mockRemoveEventListener,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
108
|
+
|
|
109
|
+
expect(mockAddEventListener).toHaveBeenCalledWith('change', expect.any(Function))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should handle media query change events', () => {
|
|
113
|
+
const mockAddEventListener = jest.fn()
|
|
114
|
+
let changeHandler: (e: MediaQueryListEvent) => void
|
|
115
|
+
|
|
116
|
+
mockMatchMedia.mockReturnValue({
|
|
117
|
+
matches: false,
|
|
118
|
+
addEventListener: (event: string, handler: any) => {
|
|
119
|
+
if (event === 'change') {
|
|
120
|
+
changeHandler = handler
|
|
121
|
+
}
|
|
122
|
+
mockAddEventListener(event, handler)
|
|
123
|
+
},
|
|
124
|
+
removeEventListener: jest.fn(),
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
128
|
+
|
|
129
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
|
|
130
|
+
|
|
131
|
+
// Simulate dark mode change
|
|
132
|
+
changeHandler!({ matches: true } as MediaQueryListEvent)
|
|
133
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('dark')
|
|
134
|
+
|
|
135
|
+
// Simulate light mode change
|
|
136
|
+
changeHandler!({ matches: false } as MediaQueryListEvent)
|
|
137
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should work in server environment (no window)', () => {
|
|
141
|
+
const originalWindow = global.window
|
|
142
|
+
delete (global as any).window
|
|
143
|
+
|
|
144
|
+
expect(() => {
|
|
145
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
146
|
+
}).not.toThrow()
|
|
147
|
+
|
|
148
|
+
global.window = originalWindow
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('dispose', () => {
|
|
153
|
+
it('should remove media query listener on dispose', () => {
|
|
154
|
+
const mockRemoveEventListener = jest.fn()
|
|
155
|
+
|
|
156
|
+
mockMatchMedia.mockReturnValue({
|
|
157
|
+
matches: false,
|
|
158
|
+
addEventListener: jest.fn(),
|
|
159
|
+
removeEventListener: mockRemoveEventListener,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
163
|
+
userPreferencesManager.dispose()
|
|
164
|
+
|
|
165
|
+
expect(mockRemoveEventListener).toHaveBeenCalledWith('change', expect.any(Function))
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should call all disposables', () => {
|
|
169
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
170
|
+
|
|
171
|
+
const mockDisposable1 = jest.fn()
|
|
172
|
+
const mockDisposable2 = jest.fn()
|
|
173
|
+
|
|
174
|
+
userPreferencesManager.disposables.add(mockDisposable1)
|
|
175
|
+
userPreferencesManager.disposables.add(mockDisposable2)
|
|
176
|
+
|
|
177
|
+
userPreferencesManager.dispose()
|
|
178
|
+
|
|
179
|
+
expect(mockDisposable1).toHaveBeenCalled()
|
|
180
|
+
expect(mockDisposable2).toHaveBeenCalled()
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('updateUserPreferences', () => {
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should update user preferences with partial data', () => {
|
|
190
|
+
const updates = { name: 'Updated Name', color: '#EC5E41' }
|
|
191
|
+
|
|
192
|
+
userPreferencesManager.updateUserPreferences(updates)
|
|
193
|
+
|
|
194
|
+
expect(mockUser.setUserPreferences).toHaveBeenCalledWith({
|
|
195
|
+
...mockUserPreferences,
|
|
196
|
+
...updates,
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should preserve existing preferences when updating', () => {
|
|
201
|
+
const updates = { animationSpeed: 0.5 }
|
|
202
|
+
|
|
203
|
+
userPreferencesManager.updateUserPreferences(updates)
|
|
204
|
+
|
|
205
|
+
expect(mockUser.setUserPreferences).toHaveBeenCalledWith({
|
|
206
|
+
...mockUserPreferences,
|
|
207
|
+
animationSpeed: 0.5,
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should handle empty updates', () => {
|
|
212
|
+
userPreferencesManager.updateUserPreferences({})
|
|
213
|
+
|
|
214
|
+
expect(mockUser.setUserPreferences).toHaveBeenCalledWith(mockUserPreferences)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('getUserPreferences', () => {
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should return complete user preferences with computed values', () => {
|
|
224
|
+
const result = userPreferencesManager.getUserPreferences()
|
|
225
|
+
|
|
226
|
+
expect(result).toEqual({
|
|
227
|
+
id: mockUserPreferences.id,
|
|
228
|
+
name: mockUserPreferences.name,
|
|
229
|
+
locale: mockUserPreferences.locale,
|
|
230
|
+
color: mockUserPreferences.color,
|
|
231
|
+
animationSpeed: mockUserPreferences.animationSpeed,
|
|
232
|
+
isSnapMode: mockUserPreferences.isSnapMode,
|
|
233
|
+
colorScheme: mockUserPreferences.colorScheme,
|
|
234
|
+
isDarkMode: false, // light mode
|
|
235
|
+
isWrapMode: mockUserPreferences.isWrapMode,
|
|
236
|
+
isDynamicResizeMode: mockUserPreferences.isDynamicSizeMode,
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should use default values for missing properties', () => {
|
|
241
|
+
const minimalPrefs: TLUserPreferences = { id: 'test-id' }
|
|
242
|
+
userPreferencesAtom.set(minimalPrefs)
|
|
243
|
+
|
|
244
|
+
const result = userPreferencesManager.getUserPreferences()
|
|
245
|
+
|
|
246
|
+
expect(result.name).toBe(defaultUserPreferences.name)
|
|
247
|
+
expect(result.color).toBe(defaultUserPreferences.color)
|
|
248
|
+
expect(result.locale).toBe(defaultUserPreferences.locale)
|
|
249
|
+
expect(result.animationSpeed).toBe(defaultUserPreferences.animationSpeed)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
describe('getIsDarkMode', () => {
|
|
254
|
+
beforeEach(() => {
|
|
255
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should return true when colorScheme is dark', () => {
|
|
259
|
+
userPreferencesAtom.set({ ...mockUserPreferences, colorScheme: 'dark' })
|
|
260
|
+
|
|
261
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(true)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('should return false when colorScheme is light', () => {
|
|
265
|
+
userPreferencesAtom.set({ ...mockUserPreferences, colorScheme: 'light' })
|
|
266
|
+
|
|
267
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(false)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should follow system preference when colorScheme is system', () => {
|
|
271
|
+
userPreferencesAtom.set({ ...mockUserPreferences, colorScheme: 'system' })
|
|
272
|
+
|
|
273
|
+
// System is light
|
|
274
|
+
userPreferencesManager.systemColorScheme.set('light')
|
|
275
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(false)
|
|
276
|
+
|
|
277
|
+
// System is dark
|
|
278
|
+
userPreferencesManager.systemColorScheme.set('dark')
|
|
279
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(true)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('should use inferDarkMode when colorScheme is undefined', () => {
|
|
283
|
+
userPreferencesAtom.set({ ...mockUserPreferences, colorScheme: undefined })
|
|
284
|
+
|
|
285
|
+
// With inferDarkMode = true
|
|
286
|
+
const managerWithInfer = new UserPreferencesManager(mockUser, true)
|
|
287
|
+
managerWithInfer.systemColorScheme.set('dark')
|
|
288
|
+
expect(managerWithInfer.getIsDarkMode()).toBe(true)
|
|
289
|
+
|
|
290
|
+
// With inferDarkMode = false
|
|
291
|
+
const managerWithoutInfer = new UserPreferencesManager(mockUser, false)
|
|
292
|
+
managerWithoutInfer.systemColorScheme.set('dark')
|
|
293
|
+
expect(managerWithoutInfer.getIsDarkMode()).toBe(false)
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
describe('individual preference getters', () => {
|
|
298
|
+
beforeEach(() => {
|
|
299
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
describe('getId', () => {
|
|
303
|
+
it('should return user id', () => {
|
|
304
|
+
expect(userPreferencesManager.getId()).toBe(mockUserPreferences.id)
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
describe('getName', () => {
|
|
309
|
+
it('should return trimmed user name', () => {
|
|
310
|
+
userPreferencesAtom.set({ ...mockUserPreferences, name: ' Test User ' })
|
|
311
|
+
expect(userPreferencesManager.getName()).toBe('Test User')
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('should return default name when name is null', () => {
|
|
315
|
+
userPreferencesAtom.set({ ...mockUserPreferences, name: null })
|
|
316
|
+
expect(userPreferencesManager.getName()).toBe(defaultUserPreferences.name)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('should return default name when name is undefined', () => {
|
|
320
|
+
userPreferencesAtom.set({ ...mockUserPreferences, name: undefined })
|
|
321
|
+
expect(userPreferencesManager.getName()).toBe(defaultUserPreferences.name)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should return default name when name is empty after trimming', () => {
|
|
325
|
+
userPreferencesAtom.set({ ...mockUserPreferences, name: ' ' })
|
|
326
|
+
expect(userPreferencesManager.getName()).toBe(defaultUserPreferences.name)
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
describe('getLocale', () => {
|
|
331
|
+
it('should return user locale', () => {
|
|
332
|
+
expect(userPreferencesManager.getLocale()).toBe(mockUserPreferences.locale)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('should return default locale when locale is null', () => {
|
|
336
|
+
userPreferencesAtom.set({ ...mockUserPreferences, locale: null })
|
|
337
|
+
expect(userPreferencesManager.getLocale()).toBe(defaultUserPreferences.locale)
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
describe('getColor', () => {
|
|
342
|
+
it('should return user color', () => {
|
|
343
|
+
expect(userPreferencesManager.getColor()).toBe(mockUserPreferences.color)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('should return default color when color is null', () => {
|
|
347
|
+
userPreferencesAtom.set({ ...mockUserPreferences, color: null })
|
|
348
|
+
expect(userPreferencesManager.getColor()).toBe(defaultUserPreferences.color)
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
describe('getAnimationSpeed', () => {
|
|
353
|
+
it('should return user animation speed', () => {
|
|
354
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(mockUserPreferences.animationSpeed)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('should return default animation speed when null', () => {
|
|
358
|
+
userPreferencesAtom.set({ ...mockUserPreferences, animationSpeed: null })
|
|
359
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(
|
|
360
|
+
defaultUserPreferences.animationSpeed
|
|
361
|
+
)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
describe('getEdgeScrollSpeed', () => {
|
|
366
|
+
it('should return user edge scroll speed', () => {
|
|
367
|
+
expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
|
|
368
|
+
mockUserPreferences.edgeScrollSpeed
|
|
369
|
+
)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('should return default edge scroll speed when null', () => {
|
|
373
|
+
userPreferencesAtom.set({ ...mockUserPreferences, edgeScrollSpeed: null })
|
|
374
|
+
expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
|
|
375
|
+
defaultUserPreferences.edgeScrollSpeed
|
|
376
|
+
)
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
describe('getIsSnapMode', () => {
|
|
381
|
+
it('should return user snap mode setting', () => {
|
|
382
|
+
expect(userPreferencesManager.getIsSnapMode()).toBe(mockUserPreferences.isSnapMode)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('should return default snap mode when null', () => {
|
|
386
|
+
userPreferencesAtom.set({ ...mockUserPreferences, isSnapMode: null })
|
|
387
|
+
expect(userPreferencesManager.getIsSnapMode()).toBe(defaultUserPreferences.isSnapMode)
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
describe('getIsWrapMode', () => {
|
|
392
|
+
it('should return user wrap mode setting', () => {
|
|
393
|
+
expect(userPreferencesManager.getIsWrapMode()).toBe(mockUserPreferences.isWrapMode)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('should return default wrap mode when null', () => {
|
|
397
|
+
userPreferencesAtom.set({ ...mockUserPreferences, isWrapMode: null })
|
|
398
|
+
expect(userPreferencesManager.getIsWrapMode()).toBe(defaultUserPreferences.isWrapMode)
|
|
399
|
+
})
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
describe('getIsDynamicResizeMode', () => {
|
|
403
|
+
it('should return user dynamic resize mode setting', () => {
|
|
404
|
+
expect(userPreferencesManager.getIsDynamicResizeMode()).toBe(
|
|
405
|
+
mockUserPreferences.isDynamicSizeMode
|
|
406
|
+
)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('should return default dynamic resize mode when null', () => {
|
|
410
|
+
userPreferencesAtom.set({ ...mockUserPreferences, isDynamicSizeMode: null })
|
|
411
|
+
expect(userPreferencesManager.getIsDynamicResizeMode()).toBe(
|
|
412
|
+
defaultUserPreferences.isDynamicSizeMode
|
|
413
|
+
)
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
describe('getIsPasteAtCursorMode', () => {
|
|
418
|
+
it('should return user paste at cursor mode setting', () => {
|
|
419
|
+
expect(userPreferencesManager.getIsPasteAtCursorMode()).toBe(
|
|
420
|
+
mockUserPreferences.isPasteAtCursorMode
|
|
421
|
+
)
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it('should return default paste at cursor mode when null', () => {
|
|
425
|
+
userPreferencesAtom.set({ ...mockUserPreferences, isPasteAtCursorMode: null })
|
|
426
|
+
expect(userPreferencesManager.getIsPasteAtCursorMode()).toBe(
|
|
427
|
+
defaultUserPreferences.isPasteAtCursorMode
|
|
428
|
+
)
|
|
429
|
+
})
|
|
430
|
+
})
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
describe('reactive behavior', () => {
|
|
434
|
+
beforeEach(() => {
|
|
435
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
it('should react to user preferences changes', () => {
|
|
439
|
+
expect(userPreferencesManager.getName()).toBe('Test User')
|
|
440
|
+
|
|
441
|
+
userPreferencesManager.updateUserPreferences({ name: 'Updated User' })
|
|
442
|
+
|
|
443
|
+
expect(userPreferencesManager.getName()).toBe('Updated User')
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('should react to system color scheme changes', () => {
|
|
447
|
+
userPreferencesAtom.set({ ...mockUserPreferences, colorScheme: 'system' })
|
|
448
|
+
|
|
449
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(false)
|
|
450
|
+
|
|
451
|
+
userPreferencesManager.systemColorScheme.set('dark')
|
|
452
|
+
|
|
453
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(true)
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('should update getUserPreferences when individual preferences change', () => {
|
|
457
|
+
const initialPrefs = userPreferencesManager.getUserPreferences()
|
|
458
|
+
expect(initialPrefs.name).toBe('Test User')
|
|
459
|
+
|
|
460
|
+
userPreferencesManager.updateUserPreferences({ name: 'Changed Name' })
|
|
461
|
+
|
|
462
|
+
const updatedPrefs = userPreferencesManager.getUserPreferences()
|
|
463
|
+
expect(updatedPrefs.name).toBe('Changed Name')
|
|
464
|
+
})
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
describe('edge cases and error handling', () => {
|
|
468
|
+
beforeEach(() => {
|
|
469
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('should handle undefined user preferences gracefully', () => {
|
|
473
|
+
userPreferencesAtom.set({} as TLUserPreferences)
|
|
474
|
+
|
|
475
|
+
expect(() => userPreferencesManager.getUserPreferences()).not.toThrow()
|
|
476
|
+
expect(userPreferencesManager.getName()).toBe(defaultUserPreferences.name)
|
|
477
|
+
expect(userPreferencesManager.getColor()).toBe(defaultUserPreferences.color)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('should handle null values in preferences', () => {
|
|
481
|
+
const nullPrefs = createMockUserPreferences({
|
|
482
|
+
name: null,
|
|
483
|
+
color: null,
|
|
484
|
+
locale: null,
|
|
485
|
+
animationSpeed: null,
|
|
486
|
+
edgeScrollSpeed: null,
|
|
487
|
+
isSnapMode: null,
|
|
488
|
+
isWrapMode: null,
|
|
489
|
+
isDynamicSizeMode: null,
|
|
490
|
+
isPasteAtCursorMode: null,
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
userPreferencesAtom.set(nullPrefs)
|
|
494
|
+
|
|
495
|
+
expect(userPreferencesManager.getName()).toBe(defaultUserPreferences.name)
|
|
496
|
+
expect(userPreferencesManager.getColor()).toBe(defaultUserPreferences.color)
|
|
497
|
+
expect(userPreferencesManager.getLocale()).toBe(defaultUserPreferences.locale)
|
|
498
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(defaultUserPreferences.animationSpeed)
|
|
499
|
+
expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
|
|
500
|
+
defaultUserPreferences.edgeScrollSpeed
|
|
501
|
+
)
|
|
502
|
+
expect(userPreferencesManager.getIsSnapMode()).toBe(defaultUserPreferences.isSnapMode)
|
|
503
|
+
expect(userPreferencesManager.getIsWrapMode()).toBe(defaultUserPreferences.isWrapMode)
|
|
504
|
+
expect(userPreferencesManager.getIsDynamicResizeMode()).toBe(
|
|
505
|
+
defaultUserPreferences.isDynamicSizeMode
|
|
506
|
+
)
|
|
507
|
+
expect(userPreferencesManager.getIsPasteAtCursorMode()).toBe(
|
|
508
|
+
defaultUserPreferences.isPasteAtCursorMode
|
|
509
|
+
)
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
it('should handle matchMedia with null response', () => {
|
|
513
|
+
// Mock matchMedia returning null (like in some environments)
|
|
514
|
+
mockMatchMedia.mockReturnValue(null)
|
|
515
|
+
|
|
516
|
+
expect(() => {
|
|
517
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
518
|
+
}).not.toThrow()
|
|
519
|
+
|
|
520
|
+
expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
it('should handle dispose gracefully in all cases', () => {
|
|
524
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
525
|
+
|
|
526
|
+
// Should not throw even if dispose is called multiple times
|
|
527
|
+
expect(() => userPreferencesManager.dispose()).not.toThrow()
|
|
528
|
+
expect(() => userPreferencesManager.dispose()).not.toThrow()
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
it('should handle empty disposables set', () => {
|
|
532
|
+
// Test in server environment where no event listeners are added
|
|
533
|
+
const originalWindow = global.window
|
|
534
|
+
delete (global as any).window
|
|
535
|
+
|
|
536
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
537
|
+
|
|
538
|
+
expect(() => userPreferencesManager.dispose()).not.toThrow()
|
|
539
|
+
expect(userPreferencesManager.disposables.size).toBe(0)
|
|
540
|
+
|
|
541
|
+
global.window = originalWindow
|
|
542
|
+
})
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
describe('integration scenarios', () => {
|
|
546
|
+
it('should work with real-world preference scenarios', () => {
|
|
547
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, true)
|
|
548
|
+
|
|
549
|
+
// User starts with system preference
|
|
550
|
+
userPreferencesManager.updateUserPreferences({ colorScheme: 'system' })
|
|
551
|
+
userPreferencesManager.systemColorScheme.set('dark')
|
|
552
|
+
|
|
553
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(true)
|
|
554
|
+
expect(userPreferencesManager.getUserPreferences().isDarkMode).toBe(true)
|
|
555
|
+
|
|
556
|
+
// User switches to light mode explicitly
|
|
557
|
+
userPreferencesManager.updateUserPreferences({ colorScheme: 'light' })
|
|
558
|
+
|
|
559
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(false)
|
|
560
|
+
expect(userPreferencesManager.getUserPreferences().isDarkMode).toBe(false)
|
|
561
|
+
|
|
562
|
+
// System changes but user preference is respected
|
|
563
|
+
userPreferencesManager.systemColorScheme.set('light')
|
|
564
|
+
|
|
565
|
+
expect(userPreferencesManager.getIsDarkMode()).toBe(false)
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
it('should handle preference updates with multiple fields', () => {
|
|
569
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
570
|
+
|
|
571
|
+
const updates = {
|
|
572
|
+
name: 'New User',
|
|
573
|
+
color: '#F2555A',
|
|
574
|
+
animationSpeed: 0.5,
|
|
575
|
+
isSnapMode: true,
|
|
576
|
+
colorScheme: 'dark' as const,
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
userPreferencesManager.updateUserPreferences(updates)
|
|
580
|
+
|
|
581
|
+
const prefs = userPreferencesManager.getUserPreferences()
|
|
582
|
+
|
|
583
|
+
expect(prefs.name).toBe('New User')
|
|
584
|
+
expect(prefs.color).toBe('#F2555A')
|
|
585
|
+
expect(prefs.animationSpeed).toBe(0.5)
|
|
586
|
+
expect(prefs.isSnapMode).toBe(true)
|
|
587
|
+
expect(prefs.colorScheme).toBe('dark')
|
|
588
|
+
expect(prefs.isDarkMode).toBe(true)
|
|
589
|
+
})
|
|
590
|
+
})
|
|
591
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { atom, computed } from '@tldraw/state'
|
|
2
|
-
import { TLUserPreferences, defaultUserPreferences } from '
|
|
3
|
-
import { TLUser } from '
|
|
2
|
+
import { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'
|
|
3
|
+
import { TLUser } from '../../../config/createTLUser'
|
|
4
4
|
|
|
5
5
|
/** @public */
|
|
6
6
|
export class UserPreferencesManager {
|
|
@@ -15,7 +15,7 @@ import { Box, SelectionHandle } from '../../primitives/Box'
|
|
|
15
15
|
import { Vec } from '../../primitives/Vec'
|
|
16
16
|
import { Geometry2d } from '../../primitives/geometry/Geometry2d'
|
|
17
17
|
import type { Editor } from '../Editor'
|
|
18
|
-
import { TLFontFace } from '../managers/FontManager'
|
|
18
|
+
import { TLFontFace } from '../managers/FontManager/FontManager'
|
|
19
19
|
import { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'
|
|
20
20
|
import { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'
|
|
21
21
|
import { SvgExportContext } from '../types/SvgExportContext'
|
|
@@ -240,6 +240,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
240
240
|
return true
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/**
|
|
244
|
+
* When the shape is resized, whether the shape's children should also be resized.
|
|
245
|
+
*
|
|
246
|
+
* @public
|
|
247
|
+
*/
|
|
248
|
+
canResizeChildren(_shape: Shape): boolean {
|
|
249
|
+
return true
|
|
250
|
+
}
|
|
251
|
+
|
|
243
252
|
/**
|
|
244
253
|
* Whether the shape can be edited in read-only mode.
|
|
245
254
|
*
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { TLDefaultDashStyle } from '@tldraw/tlschema'
|
|
2
2
|
|
|
3
|
+
/** @public */
|
|
4
|
+
export type PerfectDashTerminal = 'skip' | 'outset' | 'none'
|
|
5
|
+
|
|
3
6
|
/** @public */
|
|
4
7
|
export function getPerfectDashProps(
|
|
5
8
|
totalLength: number,
|
|
@@ -7,8 +10,8 @@ export function getPerfectDashProps(
|
|
|
7
10
|
opts: {
|
|
8
11
|
style?: TLDefaultDashStyle
|
|
9
12
|
snap?: number
|
|
10
|
-
end?:
|
|
11
|
-
start?:
|
|
13
|
+
end?: PerfectDashTerminal
|
|
14
|
+
start?: PerfectDashTerminal
|
|
12
15
|
lengthRatio?: number
|
|
13
16
|
closed?: boolean
|
|
14
17
|
forceSolid?: boolean
|
|
@@ -21,7 +21,7 @@ import { flushSync } from 'react-dom'
|
|
|
21
21
|
import { ErrorBoundary } from '../components/ErrorBoundary'
|
|
22
22
|
import { InnerShape, InnerShapeBackground } from '../components/Shape'
|
|
23
23
|
import { Editor, TLRenderingShape } from '../editor/Editor'
|
|
24
|
-
import { TLFontFace } from '../editor/managers/FontManager'
|
|
24
|
+
import { TLFontFace } from '../editor/managers/FontManager/FontManager'
|
|
25
25
|
import { ShapeUtil } from '../editor/shapes/ShapeUtil'
|
|
26
26
|
import {
|
|
27
27
|
SvgExportContext,
|