mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-23 00:33:50 +02:00
split some function in the store
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { gameLogSearchFilter } from '../gameLog';
|
||||
import {
|
||||
compareGameLogRows,
|
||||
gameLogSearchFilter,
|
||||
getGameLogCreatedAtTs
|
||||
} from '../gameLog';
|
||||
|
||||
describe('gameLogSearchFilter', () => {
|
||||
test('returns true for empty search query', () => {
|
||||
@@ -96,3 +100,87 @@ describe('gameLogSearchFilter', () => {
|
||||
expect(gameLogSearchFilter(row, 'anything')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGameLogCreatedAtTs', () => {
|
||||
test('returns millisecond timestamp from millis number', () => {
|
||||
expect(getGameLogCreatedAtTs({ created_at: 1700000000000 })).toBe(
|
||||
1700000000000
|
||||
);
|
||||
});
|
||||
|
||||
test('converts seconds to millis for small numbers', () => {
|
||||
expect(getGameLogCreatedAtTs({ created_at: 1700000000 })).toBe(
|
||||
1700000000000
|
||||
);
|
||||
});
|
||||
|
||||
test('parses ISO string via Date.parse', () => {
|
||||
const ts = getGameLogCreatedAtTs({
|
||||
created_at: '2024-01-15T12:00:00Z'
|
||||
});
|
||||
expect(ts).toBe(Date.parse('2024-01-15T12:00:00Z'));
|
||||
});
|
||||
|
||||
test('supports createdAt alias', () => {
|
||||
expect(getGameLogCreatedAtTs({ createdAt: 1700000000000 })).toBe(
|
||||
1700000000000
|
||||
);
|
||||
});
|
||||
|
||||
test('supports dt alias', () => {
|
||||
expect(getGameLogCreatedAtTs({ dt: 1700000000000 })).toBe(
|
||||
1700000000000
|
||||
);
|
||||
});
|
||||
|
||||
test('returns 0 for null/undefined row', () => {
|
||||
expect(getGameLogCreatedAtTs(null)).toBe(0);
|
||||
expect(getGameLogCreatedAtTs(undefined)).toBe(0);
|
||||
});
|
||||
|
||||
test('returns 0 for missing timestamp fields', () => {
|
||||
expect(getGameLogCreatedAtTs({})).toBe(0);
|
||||
});
|
||||
|
||||
test('returns 0 for unparseable string', () => {
|
||||
expect(getGameLogCreatedAtTs({ created_at: 'not-a-date' })).toBe(0);
|
||||
});
|
||||
|
||||
test('returns 0 for NaN number', () => {
|
||||
expect(getGameLogCreatedAtTs({ created_at: NaN })).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareGameLogRows', () => {
|
||||
test('sorts by timestamp descending (newest first)', () => {
|
||||
const a = { created_at: 2000 };
|
||||
const b = { created_at: 1000 };
|
||||
expect(compareGameLogRows(a, b)).toBeLessThan(0);
|
||||
expect(compareGameLogRows(b, a)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('equal timestamps → sorts by rowId descending', () => {
|
||||
const a = { created_at: 1000, rowId: 10 };
|
||||
const b = { created_at: 1000, rowId: 5 };
|
||||
expect(compareGameLogRows(a, b)).toBeLessThan(0);
|
||||
expect(compareGameLogRows(b, a)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('equal timestamp and rowId → sorts by uid reverse-lex', () => {
|
||||
const a = { created_at: 1000, rowId: 1, uid: 'bbb' };
|
||||
const b = { created_at: 1000, rowId: 1, uid: 'aaa' };
|
||||
expect(compareGameLogRows(a, b)).toBeLessThan(0);
|
||||
expect(compareGameLogRows(b, a)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('returns 0 for identical rows', () => {
|
||||
const row = { created_at: 1000, rowId: 1, uid: 'aaa' };
|
||||
expect(compareGameLogRows(row, row)).toBe(0);
|
||||
});
|
||||
|
||||
test('handles missing fields gracefully', () => {
|
||||
const a = {};
|
||||
const b = {};
|
||||
expect(compareGameLogRows(a, b)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,4 +73,59 @@ function gameLogSearchFilter(row, searchQuery) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export { gameLogSearchFilter };
|
||||
/**
|
||||
* Extract a millisecond timestamp from a game log row.
|
||||
* Handles numeric (seconds or millis), ISO string, and dayjs-parseable formats.
|
||||
*
|
||||
* @param {object} row
|
||||
* @returns {number} millisecond timestamp, or 0 if unparseable
|
||||
*/
|
||||
function getGameLogCreatedAtTs(row) {
|
||||
// dynamic import avoided — dayjs is a lightweight dep already used by the
|
||||
// consumer; we import it lazily to keep the module usable without bundler
|
||||
// context in tests (dayjs is a CJS/ESM dual package).
|
||||
const createdAtRaw = row?.created_at ?? row?.createdAt ?? row?.dt;
|
||||
if (typeof createdAtRaw === 'number') {
|
||||
const ts =
|
||||
createdAtRaw > 1_000_000_000_000
|
||||
? createdAtRaw
|
||||
: createdAtRaw * 1000;
|
||||
return Number.isFinite(ts) ? ts : 0;
|
||||
}
|
||||
|
||||
const createdAt = typeof createdAtRaw === 'string' ? createdAtRaw : '';
|
||||
// dayjs is imported at the call site (store) — here we do a simple
|
||||
// Date.parse fallback to stay dependency-free.
|
||||
const ts = Date.parse(createdAt);
|
||||
return Number.isFinite(ts) ? ts : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two game log rows for descending sort order.
|
||||
* Primary key: created_at timestamp (newest first).
|
||||
* Secondary: rowId (highest first).
|
||||
* Tertiary: uid string (reverse lexicographic).
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns {number} negative if a should come first, positive if b first
|
||||
*/
|
||||
function compareGameLogRows(a, b) {
|
||||
const aTs = getGameLogCreatedAtTs(a);
|
||||
const bTs = getGameLogCreatedAtTs(b);
|
||||
if (aTs !== bTs) {
|
||||
return bTs - aTs;
|
||||
}
|
||||
|
||||
const aRowId = typeof a?.rowId === 'number' ? a.rowId : 0;
|
||||
const bRowId = typeof b?.rowId === 'number' ? b.rowId : 0;
|
||||
if (aRowId !== bRowId) {
|
||||
return bRowId - aRowId;
|
||||
}
|
||||
|
||||
const aUid = typeof a?.uid === 'string' ? a.uid : '';
|
||||
const bUid = typeof b?.uid === 'string' ? b.uid : '';
|
||||
return aUid < bUid ? 1 : aUid > bUid ? -1 : 0;
|
||||
}
|
||||
|
||||
export { gameLogSearchFilter, getGameLogCreatedAtTs, compareGameLogRows };
|
||||
|
||||
Reference in New Issue
Block a user