Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 109 additions & 1 deletion packages/destination-google-sheets/__tests__/memory-sheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ export function createMemorySheets() {
return label || 'A'
}

// Slice `values` to an A1 range. `'Name'` → whole tab; `'Name'!A2:C[100]` → bounded.
function sliceByRange(values: unknown[][], range: string): unknown[][] {
const bang = range.indexOf('!')
if (bang < 0) return values
const m = range.slice(bang + 1).match(/^([A-Z]+)(\d+)?(?::([A-Z]+)(\d+)?)?$/)
if (!m) return values
const colIdx = (s: string) =>
[...s].reduce((v, ch) => v * 26 + (ch.charCodeAt(0) - 64), 0) - 1
const startCol = colIdx(m[1])
const startRow = m[2] ? Number(m[2]) - 1 : 0
const endCol = m[3] !== undefined ? colIdx(m[3]) : Infinity
const endRow = m[4] !== undefined ? Number(m[4]) - 1 : values.length - 1
const out: unknown[][] = []
for (let r = startRow; r <= Math.min(endRow, values.length - 1); r++) {
const src = values[r] ?? []
const slice: unknown[] = []
for (let c = startCol; c <= Math.min(endCol, src.length - 1); c++) slice.push(src[c])
out.push(slice)
}
return out
}

function getTab(spreadsheetId: string, range: string): SheetTab {
const ss = getSpreadsheet(spreadsheetId)
const name = parseSheetName(range)
Expand All @@ -69,6 +91,22 @@ export function createMemorySheets() {
return tab
}

function getTabBySheetId(spreadsheetId: string, sheetId: number): SheetTab {
const ss = getSpreadsheet(spreadsheetId)
for (const tab of ss.sheets.values()) {
if (tab.sheetId === sheetId) return tab
}
throw Object.assign(new Error(`Sheet not found: ${sheetId}`), { code: 400 })
}

function rowDataToValues(rowData: unknown): string[] {
const values = (rowData as { values?: unknown[] })?.values ?? []
return values.map((cell) => {
const uev = (cell as { userEnteredValue?: { stringValue?: string } })?.userEnteredValue
return uev?.stringValue ?? ''
})
}

const sheets = {
spreadsheets: {
async create(params: { requestBody?: { properties?: { title?: string } }; fields?: string }) {
Expand All @@ -83,7 +121,11 @@ export function createMemorySheets() {
async get(params: { spreadsheetId: string; fields?: string }) {
const ss = getSpreadsheet(params.spreadsheetId)
const sheetsMeta = Array.from(ss.sheets.entries()).map(([name, tab]) => ({
properties: { sheetId: tab.sheetId, title: name },
properties: {
sheetId: tab.sheetId,
title: name,
gridProperties: { rowCount: 1000, columnCount: 26 },
},
}))
return { data: { sheets: sheetsMeta } }
},
Expand Down Expand Up @@ -118,6 +160,59 @@ export function createMemorySheets() {
}
}
replies.push({})
} else if (req.appendCells) {
const ac = req.appendCells as { sheetId: number; rows?: unknown[] }
const tab = getTabBySheetId(params.spreadsheetId, ac.sheetId)
for (const row of ac.rows ?? []) tab.values.push(rowDataToValues(row))
replies.push({})
} else if (req.updateCells) {
const uc = req.updateCells as {
start?: { sheetId?: number; rowIndex?: number; columnIndex?: number }
rows?: unknown[]
}
const sheetId = uc.start?.sheetId
if (sheetId != null) {
const tab = getTabBySheetId(params.spreadsheetId, sheetId)
const rowIndex = uc.start?.rowIndex ?? 0
const rows = (uc.rows ?? []).map(rowDataToValues)
for (let i = 0; i < rows.length; i++) {
tab.values[rowIndex + i] = rows[i]
}
}
replies.push({})
} else if (req.appendDimension) {
// No-op in the fake: the backing arrays grow dynamically, so the
// grid never actually constrains writes. Accept and reply empty
// so production code paths that call appendDimension succeed.
replies.push({})
} else if (req.pasteData) {
// Parse a pasteData request and write its cells into the tab. The
// production code uses `\x1f` as column delimiter and `\n` as row
// delimiter (fixed by the API), with `PASTE_VALUES` semantics.
const pd = req.pasteData as {
coordinate?: { sheetId?: number; rowIndex?: number; columnIndex?: number }
data?: string
delimiter?: string
type?: string
}
const sheetId = pd.coordinate?.sheetId
if (sheetId != null) {
const tab = getTabBySheetId(params.spreadsheetId, sheetId)
const rowIndex = pd.coordinate?.rowIndex ?? 0
const columnIndex = pd.coordinate?.columnIndex ?? 0
const delimiter = pd.delimiter ?? '\t'
const raw = pd.data ?? ''
const rowLines = raw.length === 0 ? [] : raw.split('\n')
for (let i = 0; i < rowLines.length; i++) {
const cells = rowLines[i].split(delimiter)
const target: unknown[] = (tab.values[rowIndex + i] ?? []).slice()
for (let j = 0; j < cells.length; j++) {
target[columnIndex + j] = cells[j]
}
tab.values[rowIndex + i] = target
}
}
replies.push({})
} else {
replies.push({})
}
Expand Down Expand Up @@ -185,6 +280,19 @@ export function createMemorySheets() {
const tab = getTab(params.spreadsheetId, params.range)
return { data: { values: tab.values } }
},

async batchGet(params: { spreadsheetId: string; ranges?: string[] }) {
const ranges = params.ranges ?? []
const valueRanges = ranges.map((range) => {
try {
const tab = getTab(params.spreadsheetId, range)
return { range, values: sliceByRange(tab.values, range) }
} catch {
return { range, values: [] }
}
})
return { data: { valueRanges } }
},
},
},
} as unknown as sheets_v4.Sheets
Expand Down
Loading
Loading