mirror of
https://github.com/svm0n/datadeck.git
synced 2026-07-22 08:31:46 +00:00
Plugin is CSV-only. registerExtensions(["csv"]); SheetJS gone; the _csv_helpers/ mirror collapses to a single canonical CSV. Bundle drops 720 KB → 297 KB (-59%); parse+eval 0.9 ms → 0.5 ms. Migration plan was empirically validated before any data was touched: xlsx-to-csv-roundtrip.mjs reads each xlsx, Papa.unparse → Papa.parse, cell-by-cell compares the result to the source. All 10 checks passed across books / movies / quotes / dictionary / habit_tracker — proves the round-trip is lossless including multiline notes, stars, embedded punctuation, and unicode. migrate-xlsx-to-csv.mjs is the cutover: writes canonical csv, moves xlsx → Archive/<name>_pre-csv-migration.xlsx (recoverable), removes the _csv_helpers/ folder, rewrites data.json fileConfigs keys from *.xlsx → *.csv so per-file overrides survive. Code surface: - main.ts: onLoadFile / doSave / generateMobileFiles / renderAddEntryForm all collapsed to the csv-only path; loadXLSX + isXlsx removed. - mobile dashboards: csv-add and dataviewjs now point at the same canonical csv (used to be split because Dataview couldn't read xlsx). - regenerate-mobile-dashboards.mjs: drops the helper-folder path. - package.json: xlsx dependency removed (9 transitive packages gone). Also included in this commit (carried over from the same session): - mobile toolbar: ⚙ Columns / 📱 Mobile / 💾 Backup collapse into a ⋯ overflow menu on screens ≤ 600 px so the toolbar fits on one row. - mobile compact density: pure-CSS media-query switch for Library (2-col compact grid + mobile-md card style), tighter Kanban cards, smaller Table cells. Tests: 88 plugin + 21 mobile-dashboard, all green. Typecheck clean. Verified manually: open books.csv, edit a note (saves debounce), open on iPhone (compact library grid, ⋯ menu, csv-add form pre-fills). Class name XLSXCardView left in place to keep this commit focused on behavior; rename to CardView is a noted follow-up in handoff.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
118 lines
2.7 KiB
JavaScript
118 lines
2.7 KiB
JavaScript
/**
|
|
* Simple test for CSV parsing logic
|
|
* Run with: node test-csv-parser.mjs
|
|
*/
|
|
|
|
// Mirror of src/utils.ts → parseCSV (Papa-backed). Kept in sync by hand.
|
|
import Papa from "papaparse";
|
|
function parseCSV(raw) {
|
|
if (!raw) return { headers: [], rows: [] };
|
|
const result = Papa.parse(raw, { header: true, skipEmptyLines: true });
|
|
const headers = (result.meta.fields ?? []).map(String);
|
|
const rows = (result.data ?? []).map(r => {
|
|
const row = {};
|
|
headers.forEach(h => { row[h] = r[h] != null ? String(r[h]) : ""; });
|
|
return row;
|
|
});
|
|
return { headers, rows };
|
|
}
|
|
|
|
// Test cases
|
|
const tests = [
|
|
{
|
|
name: "Simple CSV",
|
|
input: `Name,Age,City
|
|
John,25,NYC
|
|
Jane,30,LA`,
|
|
expected: {
|
|
headers: ["Name", "Age", "City"],
|
|
rows: [
|
|
{ Name: "John", Age: "25", City: "NYC" },
|
|
{ Name: "Jane", Age: "30", City: "LA" }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "Quoted fields with commas",
|
|
input: `Title,Description
|
|
"Hello, World","A simple greeting"
|
|
Test,"No quotes needed"`,
|
|
expected: {
|
|
headers: ["Title", "Description"],
|
|
rows: [
|
|
{ Title: "Hello, World", Description: "A simple greeting" },
|
|
{ Title: "Test", Description: "No quotes needed" }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "Escaped quotes",
|
|
input: `Name,Quote
|
|
John,"He said ""Hello"""
|
|
Jane,"She replied ""Hi""!"`,
|
|
expected: {
|
|
headers: ["Name", "Quote"],
|
|
rows: [
|
|
{ Name: "John", Quote: 'He said "Hello"' },
|
|
{ Name: "Jane", Quote: 'She replied "Hi"!' }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "Empty fields",
|
|
input: `A,B,C
|
|
1,,3
|
|
,2,
|
|
,,`,
|
|
expected: {
|
|
headers: ["A", "B", "C"],
|
|
rows: [
|
|
{ A: "1", B: "", C: "3" },
|
|
{ A: "", B: "2", C: "" },
|
|
{ A: "", B: "", C: "" }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "Unicode content",
|
|
input: `Name,Emoji,Japanese
|
|
Test,🎉,こんにちは
|
|
Book,📚,日本語`,
|
|
expected: {
|
|
headers: ["Name", "Emoji", "Japanese"],
|
|
rows: [
|
|
{ Name: "Test", Emoji: "🎉", Japanese: "こんにちは" },
|
|
{ Name: "Book", Emoji: "📚", Japanese: "日本語" }
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: "Empty CSV",
|
|
input: "",
|
|
expected: { headers: [], rows: [] }
|
|
}
|
|
];
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
console.log("Running CSV parser tests...\n");
|
|
|
|
for (const test of tests) {
|
|
const result = parseCSV(test.input);
|
|
const resultStr = JSON.stringify(result);
|
|
const expectedStr = JSON.stringify(test.expected);
|
|
|
|
if (resultStr === expectedStr) {
|
|
console.log(`✓ ${test.name}`);
|
|
passed++;
|
|
} else {
|
|
console.log(`✗ ${test.name}`);
|
|
console.log(` Expected: ${expectedStr}`);
|
|
console.log(` Got: ${resultStr}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
process.exit(failed > 0 ? 1 : 0);
|