feat(import): add the import page, multi-select add and the favorite platform

This commit is contained in:
lobo committed 2026-09-29 10:20:54 +02:00
1 parent aa9e83e0cb
commit 14dd59f9e0
21 files changed
+1436 -48

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
import {describe, expect, it} from 'vitest';
import {parseCsv, parseTextList} from './imports.js';
describe('parseTextList', () => {
it('takes one title per line and skips empty lines', () => {
expect(parseTextList(' Hades \r\n\nCeleste\n')).toEqual([{title: 'Hades'}, {title: 'Celeste'}]);
expect(parseTextList('x'.repeat(150))[0].title).toHaveLength(100);
});
});
describe('parseCsv', () => {
it('reads the known columns, quotes and hours', () => {
const {rows} = parseCsv('Name,Platform,Hours,Status\n'
+ '"Hades, the game",PS5,"1,5",playing\r\n'
+ '"The ""Best"" Game",,,\n'
+ ',PC,3,\n');
expect(rows).toEqual([
{title: 'Hades, the game', platform: 'PS5', playtimeMinutes: 90, status: 'PLAYING'},
{title: 'The "Best" Game', platform: null, playtimeMinutes: null, status: null},
]);
});
it('takes the delimiter the header has most of, and minutes', () => {
const {rows} = parseCsv('title;minutes;status\nCeleste;125;unknown\n');
expect(rows).toEqual([{title: 'Celeste', platform: null, playtimeMinutes: 125, status: null}]);
});
it('keeps a line break inside quotes', () => {
expect(parseCsv('game\n"Two\nLines"\n').rows[0].title).toBe('Two\nLines');
});
it('keeps a quote inside an unquoted field as a character', () => {
expect(parseCsv('title,platform\n12" Barbarian,PC\nHades,PS5\n').rows.map((row) => row.title))
.toEqual(['12" Barbarian', 'Hades']);
});
it('needs a title column', () => {
expect(parseCsv('platform,hours\nPC,1\n')).toEqual({error: 'IMPORT.CSV_NO_TITLE'});
});
});