Skip to content

Commit e840047

Browse files
Andy MitchellAndy Mitchell
Andy Mitchell
authored and
Andy Mitchell
committed
Add moveObjectToFrontOfArray
1 parent 572f047 commit e840047

File tree

4 files changed

+154
-2
lines changed

4 files changed

+154
-2
lines changed

src/main/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export {
4747

4848
export * as EmailHelpers from "./email-helpers/index.ts";
4949
export * as PostgresHelpers from './db/postgres/index.js';
50-
50+
export * from './moveObjectToFrontOfArray.ts'
5151

5252
export type {
5353
EnsureAllMethodsAreAsync,

src/main/misc.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { convertArrayToRecord, getGlobal, promiseWithTrigger } from "./misc.ts"
22

3+
34
test('getGlobal', () => {
45
const glob = getGlobal();
56
expect(!!glob).toBe(true);
@@ -27,4 +28,5 @@ describe('convertArrayToRecord', () => {
2728
const result = convertArrayToRecord([{name: 'Bob'}], 'name');
2829
expect(result).toEqual({'Bob': {name: 'Bob'}})
2930
})
30-
})
31+
})
32+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { moveObjectToFront, moveObjectToFrontMutate } from './moveObjectToFrontOfArray.ts';
3+
4+
testMoveObjectToFront('moveObjectToFront', moveObjectToFront, false);
5+
testMoveObjectToFront('moveObjectToFrontMutate', moveObjectToFrontMutate, true);
6+
7+
8+
/**
9+
* Reusable test suite for any implementation of moveObjectToFront.
10+
*
11+
* @param name - Name of the implementation being tested.
12+
* @param moveFn - The function implementation to test.
13+
* @param mutate - Whether the function mutates the original array.
14+
*/
15+
export function testMoveObjectToFront(
16+
name: string,
17+
moveFn: <T, K extends keyof T>(arr: T[], key: K, value: T[K]) => T[],
18+
mutate: boolean
19+
) {
20+
describe(name, () => {
21+
it('moves matching item to front (single match)', () => {
22+
const arr = [
23+
{ id: 'b' },
24+
{ id: 'a' },
25+
{ id: 'c' }
26+
];
27+
const original = [...arr];
28+
const result = moveFn(arr, 'id', 'a');
29+
expect(result[0]!.id).toBe('a');
30+
if (!mutate) expect(arr).toEqual(original);
31+
});
32+
33+
it('it does not sort other keys', () => {
34+
const arr = [
35+
{ id: 'z' },
36+
{ id: 'a' },
37+
{ id: 'y' }
38+
];
39+
const original = [...arr];
40+
const result = moveFn(arr, 'id', 'a');
41+
expect(result[0]!.id).toBe('a');
42+
expect(result[1]!.id).toBe('z');
43+
expect(result[2]!.id).toBe('y');
44+
if (!mutate) expect(arr).toEqual(original);
45+
});
46+
47+
it('moves multiple matches to the front in order', () => {
48+
const arr = [
49+
{ id: 'x' },
50+
{ id: 'a' },
51+
{ id: 'b' },
52+
{ id: 'a' },
53+
{ id: 'c' }
54+
];
55+
const original = [...arr];
56+
const result = moveFn(arr, 'id', 'a');
57+
expect(result.slice(0, 2).map(obj => obj.id)).toEqual(['a', 'a']);
58+
expect(result.length).toBe(5);
59+
if (!mutate) expect(arr).toEqual(original);
60+
});
61+
62+
it('leaves array unchanged if value not found', () => {
63+
const arr = [
64+
{ id: 'x' },
65+
{ id: 'y' },
66+
{ id: 'z' }
67+
];
68+
const original = [...arr];
69+
const result = moveFn(arr, 'id', 'a');
70+
expect(result).toEqual(arr);
71+
if (!mutate) expect(arr).toEqual(original);
72+
});
73+
74+
it('works with numeric keys and values', () => {
75+
const arr = [
76+
{ rank: 2 },
77+
{ rank: 1 },
78+
{ rank: 3 },
79+
{ rank: 1 }
80+
];
81+
const original = [...arr];
82+
const result = moveFn(arr, 'rank', 1);
83+
expect(result.slice(0, 2).map(o => o.rank)).toEqual([1, 1]);
84+
if (!mutate) expect(arr).toEqual(original);
85+
});
86+
87+
it('returns empty array if input is empty', () => {
88+
const arr: { id: string }[] = [];
89+
const result = moveFn(arr, 'id', 'a');
90+
expect(result).toEqual([]);
91+
});
92+
93+
it('double check mutation', () => {
94+
const arr = [
95+
{ id: 'b' },
96+
{ id: 'a' },
97+
{ id: 'c' }
98+
];
99+
const original = [...arr];
100+
const result = moveFn(arr, 'id', 'a');
101+
if (!mutate) expect(arr).toEqual(original);
102+
if( mutate ) {
103+
expect(result).toBe(arr);
104+
expect(result).not.toBe(original);
105+
}
106+
});
107+
});
108+
}

src/main/moveObjectToFrontOfArray.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Returns a new array with all objects that match a specific key-value pair moved to the front.
3+
*
4+
* @template T - The object type in the array.
5+
* @template K - The key of the object to match.
6+
* @param {T[]} arr - The original array to process.
7+
* @param {K} key - The key to match in the object.
8+
* @param {T[K]} value - The value to match for the given key.
9+
* @returns {T[]} A new array with matching objects first, followed by the rest.
10+
*/
11+
export function moveObjectToFront<T, K extends keyof T>(
12+
arr: T[],
13+
key: K,
14+
value: T[K]
15+
): T[] {
16+
return [
17+
...arr.filter(item => item[key] === value),
18+
...arr.filter(item => item[key] !== value),
19+
];
20+
}
21+
22+
23+
/**
24+
* Moves all objects with a specific key-value pair to the front of the array (in-place).
25+
*
26+
* @template T - The object type in the array.
27+
* @template K - The key of the object to match.
28+
* @param {T[]} arr - The array to mutate and sort.
29+
* @param {K} key - The key to match in the object.
30+
* @param {T[K]} value - The value to match for the given key.
31+
* @returns {T[]} The same array with matching objects moved to the front.
32+
*/
33+
export function moveObjectToFrontMutate<T, K extends keyof T>(
34+
arr: T[],
35+
key: K,
36+
value: T[K]
37+
): T[] {
38+
return arr.sort((a, b) =>
39+
a[key] === value ? -1 : b[key] === value ? 1 : 0
40+
);
41+
}
42+

0 commit comments

Comments
 (0)