@tuturuuu/utils 0.0.3 → 0.6.1
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/CHANGELOG.md +313 -0
- package/biome.json +5 -0
- package/jsr.json +8 -8
- package/package.json +63 -32
- package/src/__tests__/ai-temp-auth.test.ts +309 -0
- package/src/__tests__/api-proxy-guard.test.ts +1451 -0
- package/src/__tests__/app-url.test.ts +270 -0
- package/src/__tests__/avatar-url.test.ts +97 -0
- package/src/__tests__/color-helper.test.ts +179 -0
- package/src/__tests__/constants.test.ts +351 -0
- package/src/__tests__/crypto.test.ts +107 -0
- package/src/__tests__/date-helper.test.ts +408 -0
- package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
- package/src/__tests__/format.test.ts +317 -0
- package/src/__tests__/html-sanitizer.test.ts +360 -0
- package/src/__tests__/interest-calculator.test.ts +336 -0
- package/src/__tests__/interest-detector.test.ts +222 -0
- package/src/__tests__/label-colors.test.ts +241 -0
- package/src/__tests__/name-helper.test.ts +158 -0
- package/src/__tests__/node-diff.test.ts +576 -0
- package/src/__tests__/notification-service.test.ts +210 -0
- package/src/__tests__/onboarding-helper.test.ts +331 -0
- package/src/__tests__/path-helper.test.ts +152 -0
- package/src/__tests__/permissions.test.tsx +81 -0
- package/src/__tests__/request-emoji-limit.test.ts +172 -0
- package/src/__tests__/search-helper.test.ts +51 -0
- package/src/__tests__/storage-display-name.test.ts +37 -0
- package/src/__tests__/storage-path.test.ts +238 -0
- package/src/__tests__/tag-utils.test.ts +205 -0
- package/src/__tests__/task-description-yjs-state.test.ts +581 -0
- package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
- package/src/__tests__/task-helper-create-task.test.ts +129 -0
- package/src/__tests__/task-helpers.test.ts +464 -0
- package/src/__tests__/task-overrides.test.ts +305 -0
- package/src/__tests__/task-reorder-cache.test.ts +74 -0
- package/src/__tests__/task-sort-keys.test.ts +36 -0
- package/src/__tests__/task-transformers.test.ts +62 -0
- package/src/__tests__/text-helper.test.ts +776 -0
- package/src/__tests__/time-helper.test.ts +70 -0
- package/src/__tests__/time-tracker-period.test.ts +55 -0
- package/src/__tests__/timezone.test.ts +117 -0
- package/src/__tests__/upstash-rest.test.ts +77 -0
- package/src/__tests__/uuid-helper.test.ts +133 -0
- package/src/__tests__/workspace-helper.test.ts +859 -0
- package/src/__tests__/workspace-limits.test.ts +255 -0
- package/src/__tests__/yjs-helper.test.ts +581 -0
- package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
- package/src/abuse-protection/__tests__/edge.test.ts +136 -0
- package/src/abuse-protection/__tests__/index.test.ts +562 -0
- package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
- package/src/abuse-protection/backend-rate-limit.ts +44 -0
- package/src/abuse-protection/constants.ts +117 -0
- package/src/abuse-protection/edge.ts +223 -0
- package/src/abuse-protection/index.ts +1545 -0
- package/src/abuse-protection/reputation.ts +587 -0
- package/src/abuse-protection/types.ts +97 -0
- package/src/abuse-protection/user-agent.ts +124 -0
- package/src/abuse-protection/user-suspension.ts +231 -0
- package/src/ai-temp-auth.ts +315 -0
- package/src/api-proxy-guard.ts +965 -0
- package/src/app-url.ts +96 -0
- package/src/avatar-url.ts +64 -0
- package/src/break-duration.ts +84 -0
- package/src/calendar-auth-token.test.ts +37 -0
- package/src/calendar-auth-token.ts +19 -0
- package/src/calendar-sync-coordination.md +197 -0
- package/src/calendar-utils.test.ts +169 -0
- package/src/calendar-utils.ts +91 -0
- package/src/color-helper.ts +110 -0
- package/src/common/nextjs.tsx +99 -0
- package/src/common/scan.tsx +15 -0
- package/src/configs/reports.ts +160 -0
- package/src/constants.ts +85 -0
- package/src/crypto.ts +21 -0
- package/src/currencies.ts +97 -0
- package/src/date-helper.ts +313 -0
- package/src/editor/convert-to-task.ts +264 -0
- package/src/editor/index.ts +5 -0
- package/src/email/__tests__/client.test.ts +141 -0
- package/src/email/__tests__/validation.test.ts +46 -0
- package/src/email/client.ts +92 -0
- package/src/email/server.ts +128 -0
- package/src/email/validation.ts +11 -0
- package/src/encryption/__tests__/calendar-events.test.ts +411 -0
- package/src/encryption/__tests__/configuration.test.ts +114 -0
- package/src/encryption/__tests__/field-encryption.test.ts +232 -0
- package/src/encryption/__tests__/key-generation.test.ts +30 -0
- package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
- package/src/encryption/__tests__/test-helpers.ts +22 -0
- package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
- package/src/encryption/encryption-service.ts +343 -0
- package/src/encryption/index.ts +25 -0
- package/src/encryption/types.ts +57 -0
- package/src/exchange-rates.ts +49 -0
- package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
- package/src/feature-flags/core.ts +322 -0
- package/src/feature-flags/data.ts +16 -0
- package/src/feature-flags/default.ts +18 -0
- package/src/feature-flags/index.ts +7 -0
- package/src/feature-flags/requestable-features.ts +79 -0
- package/src/feature-flags/types.ts +4 -0
- package/src/fetcher.ts +2 -0
- package/src/finance/index.ts +4 -0
- package/src/finance/interest-calculator.ts +456 -0
- package/src/finance/interest-detector.ts +141 -0
- package/src/finance/transform-invoice-results.ts +219 -0
- package/src/finance/wallet-permissions.test.ts +169 -0
- package/src/finance/wallet-permissions.ts +82 -0
- package/src/format.ts +120 -1
- package/src/generated/platform-build-metadata.ts +11 -0
- package/src/hooks/use-platform.ts +64 -0
- package/src/html-sanitizer.ts +155 -0
- package/src/internal-domains.ts +497 -0
- package/src/keyboard-preset.ts +109 -0
- package/src/label-colors.ts +213 -0
- package/src/launchable-apps.test.ts +126 -0
- package/src/launchable-apps.ts +490 -0
- package/src/name-helper.ts +269 -0
- package/src/next-config.test.ts +234 -0
- package/src/next-config.ts +203 -0
- package/src/node-diff.ts +375 -0
- package/src/notification-service.ts +379 -0
- package/src/nova/scores/__tests__/calculate.test.ts +254 -0
- package/src/nova/scores/calculate.ts +132 -0
- package/src/nova/submissions/check-permission.ts +132 -0
- package/src/onboarding-helper.ts +213 -0
- package/src/path-helper.ts +93 -0
- package/src/permissions.tsx +1170 -0
- package/src/plan-helpers.test.ts +188 -0
- package/src/plan-helpers.ts +80 -0
- package/src/platform-release.test.ts +74 -0
- package/src/platform-release.ts +155 -0
- package/src/portless.ts +124 -0
- package/src/priority-styles.ts +42 -0
- package/src/request-emoji-limit.ts +335 -0
- package/src/search-helper.ts +18 -0
- package/src/search.test.ts +89 -0
- package/src/search.ts +355 -0
- package/src/storage-display-name.ts +30 -0
- package/src/storage-path.ts +147 -0
- package/src/tag-utils.ts +159 -0
- package/src/task/reorder.ts +245 -0
- package/src/task/transformers.ts +149 -0
- package/src/task-date-timezone.ts +133 -0
- package/src/task-description-content.ts +240 -0
- package/src/task-helper/board.ts +193 -0
- package/src/task-helper/bulk-actions.ts +564 -0
- package/src/task-helper/personal-external-staging.ts +21 -0
- package/src/task-helper/recycle-bin.ts +202 -0
- package/src/task-helper/relationships.ts +346 -0
- package/src/task-helper/shared.ts +109 -0
- package/src/task-helper/sort-keys.ts +337 -0
- package/src/task-helper/task-hooks-basic.ts +342 -0
- package/src/task-helper/task-hooks-move.ts +264 -0
- package/src/task-helper/task-operations.ts +278 -0
- package/src/task-helper.ts +12 -0
- package/src/task-helpers.ts +241 -0
- package/src/task-list-status.ts +62 -0
- package/src/task-overrides.ts +82 -0
- package/src/task-snapshot.ts +374 -0
- package/src/text-diff.ts +81 -0
- package/src/text-helper.ts +537 -0
- package/src/time-helper.ts +63 -0
- package/src/time-tracker-period.ts +73 -0
- package/src/timeblock-helper.ts +418 -0
- package/src/timezone.ts +190 -0
- package/src/timezones.json +1271 -0
- package/src/upstash-rest.ts +56 -0
- package/src/user-helper.ts +296 -0
- package/src/uuid-helper.ts +11 -0
- package/src/workspace-handle.ts +10 -0
- package/src/workspace-helper.ts +1408 -0
- package/src/workspace-limits.ts +68 -0
- package/src/yjs-helper.ts +217 -0
- package/src/yjs-task-description.ts +81 -0
- package/tsconfig.json +3 -5
- package/tsconfig.typecheck.json +33 -0
- package/vitest.config.ts +36 -0
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/eslint.config.mjs +0 -20
- package/rollup.config.js +0 -41
- package/src/index.ts +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TaskUserOverride,
|
|
3
|
+
TaskWithRelations,
|
|
4
|
+
UserBoardListOverride,
|
|
5
|
+
} from '@tuturuuu/types';
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { isPersonallyHidden, resolveEffectiveValues } from '../task-overrides';
|
|
8
|
+
|
|
9
|
+
// Helper to create a minimal TaskWithRelations object for testing
|
|
10
|
+
function makeTask(
|
|
11
|
+
overrides: Partial<TaskWithRelations> = {}
|
|
12
|
+
): TaskWithRelations {
|
|
13
|
+
return {
|
|
14
|
+
id: 'task-1',
|
|
15
|
+
name: 'Test Task',
|
|
16
|
+
priority: 'normal',
|
|
17
|
+
end_date: '2026-03-01T00:00:00Z',
|
|
18
|
+
estimation_points: 3,
|
|
19
|
+
list: {
|
|
20
|
+
id: 'list-1',
|
|
21
|
+
name: 'To Do',
|
|
22
|
+
status: 'active',
|
|
23
|
+
board: {
|
|
24
|
+
id: 'board-1',
|
|
25
|
+
name: 'Sprint Board',
|
|
26
|
+
ws_id: 'ws-1',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
assignees: [],
|
|
30
|
+
labels: [],
|
|
31
|
+
...overrides,
|
|
32
|
+
} as TaskWithRelations;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeOverride(
|
|
36
|
+
overrides: Partial<TaskUserOverride> = {}
|
|
37
|
+
): TaskUserOverride {
|
|
38
|
+
return {
|
|
39
|
+
task_id: 'task-1',
|
|
40
|
+
user_id: 'user-1',
|
|
41
|
+
self_managed: false,
|
|
42
|
+
completed_at: null,
|
|
43
|
+
priority_override: null,
|
|
44
|
+
due_date_override: null,
|
|
45
|
+
estimation_override: null,
|
|
46
|
+
personally_unassigned: false,
|
|
47
|
+
notes: null,
|
|
48
|
+
personal_added_at: null,
|
|
49
|
+
personal_board_id: null,
|
|
50
|
+
personal_list_id: null,
|
|
51
|
+
personal_placed_at: null,
|
|
52
|
+
personal_sort_key: null,
|
|
53
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
54
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function makeBoardListOverride(
|
|
60
|
+
overrides: Partial<UserBoardListOverride> = {}
|
|
61
|
+
): UserBoardListOverride {
|
|
62
|
+
return {
|
|
63
|
+
id: 'blo-1',
|
|
64
|
+
user_id: 'user-1',
|
|
65
|
+
scope_type: 'board',
|
|
66
|
+
board_id: null,
|
|
67
|
+
list_id: null,
|
|
68
|
+
personal_status: 'not_started',
|
|
69
|
+
notes: null,
|
|
70
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
71
|
+
updated_at: '2026-01-01T00:00:00Z',
|
|
72
|
+
...overrides,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('resolveEffectiveValues', () => {
|
|
77
|
+
it('returns the original task when overrides is null', () => {
|
|
78
|
+
const task = makeTask();
|
|
79
|
+
const result = resolveEffectiveValues(task, null);
|
|
80
|
+
expect(result).toBe(task); // same reference
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns the original task when overrides is undefined', () => {
|
|
84
|
+
const task = makeTask();
|
|
85
|
+
const result = resolveEffectiveValues(task, undefined);
|
|
86
|
+
expect(result).toBe(task);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns the original task when self_managed is false', () => {
|
|
90
|
+
const task = makeTask();
|
|
91
|
+
const override = makeOverride({ self_managed: false });
|
|
92
|
+
const result = resolveEffectiveValues(task, override);
|
|
93
|
+
expect(result).toBe(task);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('applies priority override when self_managed is true', () => {
|
|
97
|
+
const task = makeTask({ priority: 'normal' });
|
|
98
|
+
const override = makeOverride({
|
|
99
|
+
self_managed: true,
|
|
100
|
+
priority_override: 'critical',
|
|
101
|
+
});
|
|
102
|
+
const result = resolveEffectiveValues(task, override);
|
|
103
|
+
expect(result.priority).toBe('critical');
|
|
104
|
+
expect(result.end_date).toBe(task.end_date); // unchanged
|
|
105
|
+
expect(result.estimation_points).toBe(task.estimation_points); // unchanged
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('applies due date override when self_managed is true', () => {
|
|
109
|
+
const task = makeTask({ end_date: '2026-03-01T00:00:00Z' });
|
|
110
|
+
const override = makeOverride({
|
|
111
|
+
self_managed: true,
|
|
112
|
+
due_date_override: '2026-04-15T00:00:00Z',
|
|
113
|
+
});
|
|
114
|
+
const result = resolveEffectiveValues(task, override);
|
|
115
|
+
expect(result.end_date).toBe('2026-04-15T00:00:00Z');
|
|
116
|
+
expect(result.priority).toBe(task.priority); // unchanged
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('applies estimation override when self_managed is true', () => {
|
|
120
|
+
const task = makeTask({ estimation_points: 3 });
|
|
121
|
+
const override = makeOverride({
|
|
122
|
+
self_managed: true,
|
|
123
|
+
estimation_override: 5,
|
|
124
|
+
});
|
|
125
|
+
const result = resolveEffectiveValues(task, override);
|
|
126
|
+
expect(result.estimation_points).toBe(5);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('applies all overrides simultaneously', () => {
|
|
130
|
+
const task = makeTask({
|
|
131
|
+
priority: 'low',
|
|
132
|
+
end_date: '2026-03-01T00:00:00Z',
|
|
133
|
+
estimation_points: 2,
|
|
134
|
+
});
|
|
135
|
+
const override = makeOverride({
|
|
136
|
+
self_managed: true,
|
|
137
|
+
priority_override: 'high',
|
|
138
|
+
due_date_override: '2026-05-01T00:00:00Z',
|
|
139
|
+
estimation_override: 7,
|
|
140
|
+
});
|
|
141
|
+
const result = resolveEffectiveValues(task, override);
|
|
142
|
+
expect(result.priority).toBe('high');
|
|
143
|
+
expect(result.end_date).toBe('2026-05-01T00:00:00Z');
|
|
144
|
+
expect(result.estimation_points).toBe(7);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('keeps team value when override field is null (self_managed true)', () => {
|
|
148
|
+
const task = makeTask({
|
|
149
|
+
priority: 'high',
|
|
150
|
+
end_date: '2026-03-01T00:00:00Z',
|
|
151
|
+
estimation_points: 4,
|
|
152
|
+
});
|
|
153
|
+
const override = makeOverride({
|
|
154
|
+
self_managed: true,
|
|
155
|
+
priority_override: null,
|
|
156
|
+
due_date_override: null,
|
|
157
|
+
estimation_override: null,
|
|
158
|
+
});
|
|
159
|
+
const result = resolveEffectiveValues(task, override);
|
|
160
|
+
expect(result.priority).toBe('high');
|
|
161
|
+
expect(result.end_date).toBe('2026-03-01T00:00:00Z');
|
|
162
|
+
expect(result.estimation_points).toBe(4);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('does not mutate the original task', () => {
|
|
166
|
+
const task = makeTask({ priority: 'low' });
|
|
167
|
+
const override = makeOverride({
|
|
168
|
+
self_managed: true,
|
|
169
|
+
priority_override: 'critical',
|
|
170
|
+
});
|
|
171
|
+
const result = resolveEffectiveValues(task, override);
|
|
172
|
+
expect(result).not.toBe(task);
|
|
173
|
+
expect(task.priority).toBe('low'); // original unchanged
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('isPersonallyHidden', () => {
|
|
178
|
+
it('returns false when no overrides', () => {
|
|
179
|
+
const task = makeTask();
|
|
180
|
+
expect(isPersonallyHidden(task, null, [])).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('returns false when overrides have no hiding flags', () => {
|
|
184
|
+
const task = makeTask();
|
|
185
|
+
const override = makeOverride();
|
|
186
|
+
expect(isPersonallyHidden(task, override, [])).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('returns true when completed_at is set', () => {
|
|
190
|
+
const task = makeTask();
|
|
191
|
+
const override = makeOverride({
|
|
192
|
+
completed_at: '2026-02-10T00:00:00Z',
|
|
193
|
+
});
|
|
194
|
+
expect(isPersonallyHidden(task, override, [])).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('returns true when personally_unassigned is true', () => {
|
|
198
|
+
const task = makeTask();
|
|
199
|
+
const override = makeOverride({
|
|
200
|
+
personally_unassigned: true,
|
|
201
|
+
});
|
|
202
|
+
expect(isPersonallyHidden(task, override, [])).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('returns true when board override status is done', () => {
|
|
206
|
+
const task = makeTask();
|
|
207
|
+
const boardOverride = makeBoardListOverride({
|
|
208
|
+
scope_type: 'board',
|
|
209
|
+
board_id: 'board-1',
|
|
210
|
+
personal_status: 'done',
|
|
211
|
+
});
|
|
212
|
+
expect(isPersonallyHidden(task, null, [boardOverride])).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('returns true when board override status is closed', () => {
|
|
216
|
+
const task = makeTask();
|
|
217
|
+
const boardOverride = makeBoardListOverride({
|
|
218
|
+
scope_type: 'board',
|
|
219
|
+
board_id: 'board-1',
|
|
220
|
+
personal_status: 'closed',
|
|
221
|
+
});
|
|
222
|
+
expect(isPersonallyHidden(task, null, [boardOverride])).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns false when board override status is in_progress', () => {
|
|
226
|
+
const task = makeTask();
|
|
227
|
+
const boardOverride = makeBoardListOverride({
|
|
228
|
+
scope_type: 'board',
|
|
229
|
+
board_id: 'board-1',
|
|
230
|
+
personal_status: 'in_progress',
|
|
231
|
+
});
|
|
232
|
+
expect(isPersonallyHidden(task, null, [boardOverride])).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('returns true when list override status is done', () => {
|
|
236
|
+
const task = makeTask();
|
|
237
|
+
const listOverride = makeBoardListOverride({
|
|
238
|
+
scope_type: 'list',
|
|
239
|
+
list_id: 'list-1',
|
|
240
|
+
personal_status: 'done',
|
|
241
|
+
});
|
|
242
|
+
expect(isPersonallyHidden(task, null, [listOverride])).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('returns true when list override status is closed', () => {
|
|
246
|
+
const task = makeTask();
|
|
247
|
+
const listOverride = makeBoardListOverride({
|
|
248
|
+
scope_type: 'list',
|
|
249
|
+
list_id: 'list-1',
|
|
250
|
+
personal_status: 'closed',
|
|
251
|
+
});
|
|
252
|
+
expect(isPersonallyHidden(task, null, [listOverride])).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('returns false when board override is for a different board', () => {
|
|
256
|
+
const task = makeTask();
|
|
257
|
+
const boardOverride = makeBoardListOverride({
|
|
258
|
+
scope_type: 'board',
|
|
259
|
+
board_id: 'other-board',
|
|
260
|
+
personal_status: 'done',
|
|
261
|
+
});
|
|
262
|
+
expect(isPersonallyHidden(task, null, [boardOverride])).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('returns false when list override is for a different list', () => {
|
|
266
|
+
const task = makeTask();
|
|
267
|
+
const listOverride = makeBoardListOverride({
|
|
268
|
+
scope_type: 'list',
|
|
269
|
+
list_id: 'other-list',
|
|
270
|
+
personal_status: 'done',
|
|
271
|
+
});
|
|
272
|
+
expect(isPersonallyHidden(task, null, [listOverride])).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('handles task with no list', () => {
|
|
276
|
+
const task = makeTask({ list: null as any });
|
|
277
|
+
const boardOverride = makeBoardListOverride({
|
|
278
|
+
scope_type: 'board',
|
|
279
|
+
board_id: 'board-1',
|
|
280
|
+
personal_status: 'done',
|
|
281
|
+
});
|
|
282
|
+
expect(isPersonallyHidden(task, null, [boardOverride])).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('handles task with no board on list', () => {
|
|
286
|
+
const task = makeTask({
|
|
287
|
+
list: { id: 'list-1', name: 'Test', status: 'active', board: null },
|
|
288
|
+
} as any);
|
|
289
|
+
const boardOverride = makeBoardListOverride({
|
|
290
|
+
scope_type: 'board',
|
|
291
|
+
board_id: 'board-1',
|
|
292
|
+
personal_status: 'done',
|
|
293
|
+
});
|
|
294
|
+
expect(isPersonallyHidden(task, null, [boardOverride])).toBe(false);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('returns true when any hiding condition is met (completion takes priority)', () => {
|
|
298
|
+
const task = makeTask();
|
|
299
|
+
const override = makeOverride({
|
|
300
|
+
completed_at: '2026-02-10T00:00:00Z',
|
|
301
|
+
personally_unassigned: true,
|
|
302
|
+
});
|
|
303
|
+
expect(isPersonallyHidden(task, override, [])).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
mergeOptimisticReorderedTaskIntoCache,
|
|
6
|
+
mergeServerReorderedTaskIntoCache,
|
|
7
|
+
} from '../task/reorder';
|
|
8
|
+
|
|
9
|
+
function createTask(overrides: Partial<Task> = {}): Task {
|
|
10
|
+
return {
|
|
11
|
+
id: 'task-1',
|
|
12
|
+
display_number: 1,
|
|
13
|
+
name: 'Task',
|
|
14
|
+
list_id: 'todo-list',
|
|
15
|
+
sort_key: 1_000_000,
|
|
16
|
+
created_at: '2026-05-07T00:00:00.000Z',
|
|
17
|
+
...overrides,
|
|
18
|
+
} as Task;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('reorder task cache helpers', () => {
|
|
22
|
+
it('marks optimistic list moves so stale list loads cannot move the card back', () => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
vi.setSystemTime(new Date('2026-05-07T01:00:00.000Z'));
|
|
25
|
+
|
|
26
|
+
const [task] = mergeOptimisticReorderedTaskIntoCache([createTask()], {
|
|
27
|
+
taskId: 'task-1',
|
|
28
|
+
newListId: 'next-list',
|
|
29
|
+
newSortKey: 2_000_000,
|
|
30
|
+
targetListStatus: null,
|
|
31
|
+
}) as Array<Task & { _localMutationAt?: number }>;
|
|
32
|
+
|
|
33
|
+
expect(task).toEqual(
|
|
34
|
+
expect.objectContaining({
|
|
35
|
+
id: 'task-1',
|
|
36
|
+
list_id: 'next-list',
|
|
37
|
+
sort_key: 2_000_000,
|
|
38
|
+
_localMutationAt: new Date('2026-05-07T01:00:00.000Z').getTime(),
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
vi.useRealTimers();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('keeps a fresh local marker after the server confirms a reorder', () => {
|
|
46
|
+
vi.useFakeTimers();
|
|
47
|
+
vi.setSystemTime(new Date('2026-05-07T01:00:10.000Z'));
|
|
48
|
+
|
|
49
|
+
const [task] = mergeServerReorderedTaskIntoCache(
|
|
50
|
+
[
|
|
51
|
+
createTask({
|
|
52
|
+
list_id: 'next-list',
|
|
53
|
+
sort_key: 2_000_000,
|
|
54
|
+
_localMutationAt: new Date('2026-05-07T01:00:00.000Z').getTime(),
|
|
55
|
+
} as Partial<Task>),
|
|
56
|
+
],
|
|
57
|
+
createTask({
|
|
58
|
+
list_id: 'next-list',
|
|
59
|
+
sort_key: 2_500_000,
|
|
60
|
+
})
|
|
61
|
+
) as Array<Task & { _localMutationAt?: number }>;
|
|
62
|
+
|
|
63
|
+
expect(task).toEqual(
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
id: 'task-1',
|
|
66
|
+
list_id: 'next-list',
|
|
67
|
+
sort_key: 2_500_000,
|
|
68
|
+
_localMutationAt: new Date('2026-05-07T01:00:10.000Z').getTime(),
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
vi.useRealTimers();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
calculateBottomSortKey,
|
|
4
|
+
calculateTopSortKey,
|
|
5
|
+
getSortKeyConfig,
|
|
6
|
+
resetSortKeySequence,
|
|
7
|
+
} from '../task-helper';
|
|
8
|
+
|
|
9
|
+
describe('task sort key helpers', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
resetSortKeySequence();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('places a new task before the current first task', () => {
|
|
15
|
+
const nextSortKey = 5_000_000;
|
|
16
|
+
const sortKey = calculateTopSortKey(nextSortKey);
|
|
17
|
+
|
|
18
|
+
expect(sortKey).toBeGreaterThan(0);
|
|
19
|
+
expect(sortKey).toBeLessThan(nextSortKey);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('uses the default sort key when inserting into an empty list', () => {
|
|
23
|
+
const { DEFAULT } = getSortKeyConfig();
|
|
24
|
+
|
|
25
|
+
expect(calculateTopSortKey(null)).toBe(DEFAULT + 1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('places a new task after the current last task', () => {
|
|
29
|
+
const previousSortKey = 5_000_000;
|
|
30
|
+
const { BASE_UNIT } = getSortKeyConfig();
|
|
31
|
+
|
|
32
|
+
expect(calculateBottomSortKey(previousSortKey)).toBe(
|
|
33
|
+
previousSortKey + BASE_UNIT + 1
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { transformTaskRecord } from '../task/transformers';
|
|
3
|
+
|
|
4
|
+
describe('transformTaskRecord', () => {
|
|
5
|
+
it('keeps flat relation arrays from workspace task API responses', () => {
|
|
6
|
+
const transformed = transformTaskRecord({
|
|
7
|
+
id: 'task-1',
|
|
8
|
+
name: 'Task',
|
|
9
|
+
list_id: 'list-1',
|
|
10
|
+
display_number: 1,
|
|
11
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
12
|
+
assignees: [{ id: 'user-1', display_name: 'Alice' }],
|
|
13
|
+
labels: [{ id: 'label-1', name: 'Backend', color: '#111111' }],
|
|
14
|
+
projects: [{ id: 'project-1', name: 'Platform', status: 'active' }],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(transformed.assignees).toEqual([
|
|
18
|
+
{ id: 'user-1', display_name: 'Alice', user_id: 'user-1' },
|
|
19
|
+
]);
|
|
20
|
+
expect(transformed.labels).toEqual([
|
|
21
|
+
{ id: 'label-1', name: 'Backend', color: '#111111' },
|
|
22
|
+
]);
|
|
23
|
+
expect(transformed.projects).toEqual([
|
|
24
|
+
{ id: 'project-1', name: 'Platform', status: 'active' },
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('normalizes nested relation arrays from supabase join responses', () => {
|
|
29
|
+
const transformed = transformTaskRecord({
|
|
30
|
+
id: 'task-1',
|
|
31
|
+
name: 'Task',
|
|
32
|
+
list_id: 'list-1',
|
|
33
|
+
display_number: 1,
|
|
34
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
35
|
+
assignees: [
|
|
36
|
+
{
|
|
37
|
+
user: { id: 'user-1', display_name: 'Alice' },
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
labels: [
|
|
41
|
+
{
|
|
42
|
+
label: { id: 'label-1', name: 'Backend', color: '#111111' },
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
projects: [
|
|
46
|
+
{
|
|
47
|
+
project: { id: 'project-1', name: 'Platform', status: 'active' },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(transformed.assignees).toEqual([
|
|
53
|
+
{ id: 'user-1', display_name: 'Alice', user_id: 'user-1' },
|
|
54
|
+
]);
|
|
55
|
+
expect(transformed.labels).toEqual([
|
|
56
|
+
{ id: 'label-1', name: 'Backend', color: '#111111' },
|
|
57
|
+
]);
|
|
58
|
+
expect(transformed.projects).toEqual([
|
|
59
|
+
{ id: 'project-1', name: 'Platform', status: 'active' },
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
});
|