@tuturuuu/utils 0.28.0 → 0.29.0

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.
@@ -22,7 +22,7 @@ describe('sortTasksByCriterion', () => {
22
22
  expect(parseTaskSortBy(null)).toBeUndefined();
23
23
  });
24
24
 
25
- it('sorts every priority direction with unset priorities last', () => {
25
+ it('sorts every priority direction with unset priorities first by default', () => {
26
26
  const tasks = [
27
27
  task('none'),
28
28
  task('high', { priority: 'high' }),
@@ -33,12 +33,31 @@ describe('sortTasksByCriterion', () => {
33
33
 
34
34
  expect(
35
35
  sortTasksByCriterion(tasks, 'priority-high').map(({ id }) => id)
36
- ).toEqual(['critical', 'high', 'normal', 'low', 'none']);
36
+ ).toEqual(['none', 'critical', 'high', 'normal', 'low']);
37
37
  expect(
38
38
  sortTasksByCriterion(tasks, 'priority-low').map(({ id }) => id)
39
- ).toEqual(['low', 'normal', 'high', 'critical', 'none']);
39
+ ).toEqual(['none', 'low', 'normal', 'high', 'critical']);
40
40
  });
41
41
 
42
+ it.each(['priority-high', 'priority-low'] as const)(
43
+ 'keeps null and undefined priorities last when requested: %s',
44
+ (sortBy) => {
45
+ const tasks = [
46
+ task('null', { priority: null }),
47
+ task('set', { priority: 'high' }),
48
+ task('undefined'),
49
+ ];
50
+ expect(
51
+ sortTasksByCriterion(tasks, sortBy, 'last').map((t) => t.id)
52
+ ).toEqual(['set', 'null', 'undefined']);
53
+ expect(sortTasksByCriterion(tasks, sortBy).map((t) => t.id)).toEqual([
54
+ 'null',
55
+ 'undefined',
56
+ 'set',
57
+ ]);
58
+ }
59
+ );
60
+
42
61
  it('keeps missing due dates and estimations last in both directions', () => {
43
62
  const tasks = [
44
63
  task('none'),
@@ -1,5 +1,14 @@
1
1
  import type { TaskPriority } from '@tuturuuu/types/primitives/Priority';
2
2
 
3
+ export const TASK_UNPRIORITIZED_POSITION_CONFIG_ID =
4
+ 'TASK_UNPRIORITIZED_POSITION';
5
+ export type UnprioritizedPosition = 'first' | 'last';
6
+ export function normalizeUnprioritizedPosition(
7
+ value: unknown
8
+ ): UnprioritizedPosition {
9
+ return value === 'last' ? 'last' : 'first';
10
+ }
11
+
3
12
  export type TaskSortBy =
4
13
  | 'name-asc'
5
14
  | 'name-desc'
@@ -78,11 +87,29 @@ function compareCreatedFallback(a: SortableTask, b: SortableTask) {
78
87
  return created || a.id.localeCompare(b.id);
79
88
  }
80
89
 
90
+ export function compareUnprioritizedPlacement(
91
+ a: TaskPriority | null | undefined,
92
+ b: TaskPriority | null | undefined,
93
+ position: UnprioritizedPosition = 'first'
94
+ ) {
95
+ if ((a == null) === (b == null)) return 0;
96
+ return (a == null ? -1 : 1) * (position === 'first' ? 1 : -1);
97
+ }
98
+
81
99
  export function compareTasksByCriterion(
82
100
  a: SortableTask,
83
101
  b: SortableTask,
84
- sortBy: TaskSortBy
102
+ sortBy: TaskSortBy,
103
+ unprioritizedPosition: UnprioritizedPosition = 'first'
85
104
  ) {
105
+ if (sortBy === 'priority-high' || sortBy === 'priority-low') {
106
+ const placement = compareUnprioritizedPlacement(
107
+ a.priority,
108
+ b.priority,
109
+ unprioritizedPosition
110
+ );
111
+ if (placement) return placement;
112
+ }
86
113
  let result = 0;
87
114
 
88
115
  switch (sortBy) {
@@ -155,8 +182,11 @@ export function compareTasksByCriterion(
155
182
 
156
183
  export function sortTasksByCriterion<T extends SortableTask>(
157
184
  tasks: readonly T[],
158
- sortBy: TaskSortBy | null | undefined
185
+ sortBy: TaskSortBy | null | undefined,
186
+ unprioritizedPosition: UnprioritizedPosition = 'first'
159
187
  ) {
160
188
  if (!sortBy) return [...tasks];
161
- return [...tasks].sort((a, b) => compareTasksByCriterion(a, b, sortBy));
189
+ return [...tasks].sort((a, b) =>
190
+ compareTasksByCriterion(a, b, sortBy, unprioritizedPosition)
191
+ );
162
192
  }
@@ -702,7 +702,6 @@ export async function resolveWorkspaceIdForPrincipal({
702
702
  if (wsId.trim().toLowerCase() !== PERSONAL_WORKSPACE_SLUG) {
703
703
  return normalizeWorkspaceId(wsId, authorizationClient);
704
704
  }
705
-
706
705
  const { data: creatorWorkspace, error: creatorWorkspaceError } =
707
706
  await authorizationClient
708
707
  .from('workspaces')
@@ -710,7 +709,6 @@ export async function resolveWorkspaceIdForPrincipal({
710
709
  .eq('personal', true)
711
710
  .eq('creator_id', principal.id)
712
711
  .maybeSingle();
713
-
714
712
  if (!creatorWorkspaceError && creatorWorkspace?.id) {
715
713
  return creatorWorkspace.id;
716
714
  }
@@ -725,7 +723,9 @@ export async function resolveWorkspaceIdForPrincipal({
725
723
  .maybeSingle();
726
724
 
727
725
  if (memberWorkspaceError || !memberWorkspace?.id) {
728
- throw new Error('Personal workspace not found');
726
+ throw creatorWorkspaceError || memberWorkspaceError
727
+ ? new WorkspaceResolutionError('Personal workspace lookup failed')
728
+ : new WorkspaceNotFoundError('Personal workspace not found');
729
729
  }
730
730
 
731
731
  return memberWorkspace.id;
@@ -55,7 +55,7 @@ export async function checkWorkspaceCreationLimit(
55
55
  canCreate: false,
56
56
  currentCount,
57
57
  limit: MAX_WORKSPACES_FOR_FREE_USERS,
58
- errorMessage: `You have reached the maximum limit of ${MAX_WORKSPACES_FOR_FREE_USERS} workspaces. Please upgrade to a paid plan or contact the Tuturuuu team for more information.`,
58
+ errorMessage: `You have reached the maximum limit of ${MAX_WORKSPACES_FOR_FREE_USERS} workspaces. Upgrading does not increase this account limit. Contact the Tuturuuu team if you need more workspaces.`,
59
59
  errorCode: WORKSPACE_LIMIT_ERROR_CODE,
60
60
  };
61
61
  }