diff --git a/README.md b/README.md index b960afd..15c7d86 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Full docs are available at [https://theoephraim.github.io/node-google-spreadshee ------------- -> 🌈 **Installation** - `npm i google-spreadsheet --save` OR `yarn add google-spreadsheet` +> 🌈 **Installation** - `npm i google-spreadsheet --save` ## Examples _the following examples are meant to give you an idea of just some of the things you can do_ @@ -76,7 +76,7 @@ await rows[1].save(); // save updates await rows[1].delete(); // delete a row ``` More info: -- [GoogleSpreadsheetWorksheet > Working With Rows](https://theoephraim.github.io/node-google-spreadsheet/#/classes/google-spreadsheet-worksheet?working-with-rows) +- [GoogleSpreadsheetWorksheet > Working With Rows](https://theoephraim.github.io/node-google-spreadsheet/#/classes/google-spreadsheet-worksheet#working-with-rows) - [GoogleSpreadsheetRow](https://theoephraim.github.io/node-google-spreadsheet/#/classes/google-spreadsheet-row) @@ -99,7 +99,7 @@ c6.note = 'This is a note!'; await sheet.saveUpdatedCells(); // save all updates in one call ``` More info: -- [GoogleSpreadsheetWorksheet > Working With Cells](https://theoephraim.github.io/node-google-spreadsheet/#/classes/google-spreadsheet-worksheet?working-with-cells) +- [GoogleSpreadsheetWorksheet > Working With Cells](https://theoephraim.github.io/node-google-spreadsheet/#/classes/google-spreadsheet-worksheet#working-with-cells) - [GoogleSpreadsheetCell](https://theoephraim.github.io/node-google-spreadsheet/#/classes/google-spreadsheet-row) diff --git a/docs/README.md b/docs/README.md index 444ad97..ac3e4b9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ _Welcome to the docs site for_ - row-based API - read, update, delete (based on the old v3 row-based calls) - managing worksheets - add, remove, resize, change title, formatting -?> 🌈 **Installation** - `npm i google-spreadsheet --save` OR `yarn add google-spreadsheet` +?> 🌈 **Installation** - `npm i google-spreadsheet --save` ## Examples _the following examples are meant to give you an idea of just some of the things you can do_ @@ -72,7 +72,7 @@ await rows[1].save(); // save updates await rows[1].delete(); // delete a row ``` More info: -- [GoogleSpreadsheetWorksheet > Working With Rows](classes/google-spreadsheet-worksheet?working-with-rows) +- [GoogleSpreadsheetWorksheet > Working With Rows](classes/google-spreadsheet-worksheet#working-with-rows) - [GoogleSpreadsheetRow](classes/google-spreadsheet-row) @@ -95,7 +95,7 @@ c6.note = 'This is a note!'; await sheet.saveUpdatedCells(); // save all updates in one call ``` More info: -- [GoogleSpreadsheetWorksheet > Working With Cells](classes/google-spreadsheet-worksheet?working-with-cells) +- [GoogleSpreadsheetWorksheet > Working With Cells](classes/google-spreadsheet-worksheet#working-with-cells) - [GoogleSpreadsheetCell](classes/google-spreadsheet-row) diff --git a/lib/GoogleSpreadsheetRow.js b/lib/GoogleSpreadsheetRow.js index b00162b..80db238 100644 --- a/lib/GoogleSpreadsheetRow.js +++ b/lib/GoogleSpreadsheetRow.js @@ -35,7 +35,7 @@ class GoogleSpreadsheetRow { const response = await this._sheet._spreadsheet.axios.request({ method: 'put', - url: `/values/${this.a1Range}`, + url: `/values/${encodeURIComponent(this.a1Range)}`, params: { valueInputOption: 'USER_ENTERED', // other option is RAW includeValuesInResponse: true, diff --git a/lib/GoogleSpreadsheetWorksheet.js b/lib/GoogleSpreadsheetWorksheet.js index 694c6d6..71668bd 100644 --- a/lib/GoogleSpreadsheetWorksheet.js +++ b/lib/GoogleSpreadsheetWorksheet.js @@ -280,7 +280,13 @@ class GoogleSpreadsheetWorksheet { async loadHeaderRow() { const rows = await this.getCellsInRange(`A1:${this.lastColumnLetter}1`); + if (!rows) { + throw new Error('No values in the header row - fill the first row with header values before trying to interact with rows'); + } this.headerValues = _.map(rows[0], (header) => header.trim()); + if (!_.compact(this.headerValues).length) { + throw new Error('All your header cells are blank - fill the first row with header values before trying to interact with rows'); + } checkForDuplicateHeaders(this.headerValues); } @@ -292,6 +298,10 @@ class GoogleSpreadsheetWorksheet { const trimmedHeaderValues = _.map(headerValues, (h) => h.trim()); checkForDuplicateHeaders(trimmedHeaderValues); + if (!_.compact(trimmedHeaderValues).length) { + throw new Error('All your header cells are blank -'); + } + const response = await this._spreadsheet.axios.request({ method: 'put', url: `/values/${encodeURIComponent(this.a1SheetName)}!A1`, @@ -302,7 +312,11 @@ class GoogleSpreadsheetWorksheet { data: { range: `${this.a1SheetName}!A1`, majorDimension: 'ROWS', - values: [trimmedHeaderValues], + values: [[ + ...trimmedHeaderValues, + // pad the rest of the row with empty values to clear them all out + ..._.times(this.columnCount - trimmedHeaderValues.length, () => ''), + ]], }, }); this.headerValues = response.data.updatedData.values[0]; @@ -759,7 +773,7 @@ class GoogleSpreadsheetWorksheet { async clear() { // clears all the data in the sheet // sheet name without ie 'sheet1' rather than 'sheet1'!A1:B5 is all cells - await this._spreadsheet.axios.post(`/values/${this.a1SheetName}:clear`); + await this._spreadsheet.axios.post(`/values/${encodeURIComponent(this.a1SheetName)}:clear`); this.resetLocalCache(true); } } diff --git a/package.json b/package.json index e016d88..6bf0993 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Theo Ephraim (https://theoephraim.com)", "name": "google-spreadsheet", "description": "Google Sheets API (v4) -- simple interface to read/write data and manage sheets", - "version": "3.0.4", + "version": "3.0.5", "license": "Unlicense", "keywords": [ "google spreadsheets", diff --git a/test/manage.test.js b/test/manage.test.js index 7ddcd98..8178360 100644 --- a/test/manage.test.js +++ b/test/manage.test.js @@ -103,7 +103,7 @@ describe('Managing doc info and sheets', () => { let sheet; beforeAll(async () => { - sheet = await doc.addSheet({}); + sheet = await doc.addSheet({ title: `Spécial CнArs - ${+new Date()}` }); }); afterAll(async () => { await sheet.delete(); diff --git a/test/rows.test.js b/test/rows.test.js index fd00d72..1af4c41 100644 --- a/test/rows.test.js +++ b/test/rows.test.js @@ -21,7 +21,10 @@ const INITIAL_DATA = [ describe('Row-based operations', () => { beforeAll(async () => { await doc.useServiceAccountAuth(creds); - sheet = await doc.addSheet({ headers: HEADERS }); + sheet = await doc.addSheet({ + headers: HEADERS, + title: `Spécial CнArs - ${+new Date()}`, // some urls have sheet title in them + }); for (let i = 0; i < INITIAL_DATA.length; i++) { await sheet.addRow(INITIAL_DATA[i]); } @@ -171,6 +174,14 @@ describe('Row-based operations', () => { sheet.loadCells('A1:E1'); }); + it('clears the entire header row when setting new values', async () => { + await sheet.setHeaderRow(['col1', 'col2', 'col3', 'col4', 'col5', 'col6', 'col7', 'col8']); + await sheet.setHeaderRow(['new1', 'new2']); + sheet.resetLocalCache(true); + await sheet.loadHeaderRow(); + expect(sheet.headerValues.length).toBe(2); + }); + it('allows empty headers', async () => { await sheet.setHeaderRow(['', 'col1', '', 'col2']); rows = await sheet.getRows(); @@ -188,6 +199,12 @@ describe('Row-based operations', () => { it('throws an error if setting duplicate headers', async () => { await expect(sheet.setHeaderRow(['col1', 'col1'])).rejects.toThrow(); }); + it('throws an error if setting empty headers', async () => { + await expect(sheet.setHeaderRow([])).rejects.toThrow(); + }); + it('throws an error if setting empty headers after trimming', async () => { + await expect(sheet.setHeaderRow([' '])).rejects.toThrow(); + }); it('throws an error if duplicate headers already exist', async () => { await sheet.loadCells('A1:C1'); @@ -198,23 +215,25 @@ describe('Row-based operations', () => { sheet.resetLocalCache(true); // forget the header values await expect(sheet.getRows()).rejects.toThrow(); }); - }); - // These api calls include the range in the URL, which includes the sheet name - // so it's possible to have encoding issues where you wouldn't otherwise - describe('special character support', () => { - let specialCharSheet; - beforeAll(async () => { - specialCharSheet = await doc.addSheet({ title: `Активные - ${+new Date()}` }); - }); - afterAll(async () => { - await specialCharSheet.delete(); + it('throws if headers are all blank', async () => { + await sheet.loadCells('A1:C1'); + sheet.getCellByA1('A1').value = ''; + sheet.getCellByA1('B1').value = ''; + sheet.getCellByA1('C1').value = ''; + await sheet.saveUpdatedCells(); + sheet.resetLocalCache(true); // forget the header values + await expect(sheet.getRows()).rejects.toThrow(); }); - it('can handle sheets with special characters', async () => { - await specialCharSheet.setHeaderRow(['Ак', 'тив', 'ные']); - await specialCharSheet.addRow([1, 2, 3]); - await specialCharSheet.getRows(); + it('throws if headers are all blank after trimming spaces', async () => { + await sheet.loadCells('A1:C1'); + sheet.getCellByA1('A1').value = ''; + sheet.getCellByA1('B1').value = ' '; + sheet.getCellByA1('C1').value = ''; + await sheet.saveUpdatedCells(); + sheet.resetLocalCache(true); // forget the header values + await expect(sheet.getRows()).rejects.toThrow(); }); }); });