Open17

aside をつかってみる

こっこさんこっこさん

初期設定

npx @google/aside init

設定項目

  • Project Title
  • package.json を使うか
  • clasp にログイン
  • Script ID (オプション)
  • 本番環境の Script ID (オプション)
こっこさんこっこさん

Project Title をファイル名にもつスプレッドシートがマイドライブ直下に作成される。

こっこさんこっこさん

package.json には次のスクリプトが登録されている

  "scripts": {
    "clean": "rimraf build dist",
    "lint": "npm run license && eslint --fix --no-error-on-unmatched-pattern src/ test/",
    "bundle": "rollup --no-treeshake -c rollup.config.mjs",
    "build": "npm run clean && npm run bundle && ncp appsscript.json dist/appsscript.json",
    "license": "license-check-and-add add -f license-config.json",
    "test": "jest test/ --passWithNoTests --detectOpenHandles",
    "deploy": "npm run lint && npm run test && npm run build && ncp .clasp-dev.json .clasp.json && clasp push -f",
    "deploy:prod": "npm run lint && npm run test && npm run build && ncp .clasp-prod.json .clasp.json && clasp push"
  },
こっこさんこっこさん

Prettier、ESLint、Jestが設定済み。

.gitignore、.editorconfig が設定されているのもよき

こっこさんこっこさん

インストール時に下記のエラーが出てしまう

Error: ENOENT: no such file or directory, lstat 'dist/.clasp.json'
こっこさんこっこさん
init() (app.ts)
→ handleClasp() (app.ts)
→ ClaspHelper.create() (clasp-helper.ts called at app.ts L325)
→ ClaspHelper.arrangeFiles() (clasp-helper.ts called at L128)
→ await fs.move(path.join('./dist', '.clasp.json'), '.clasp-dev.json'); (L146)
こっこさんこっこさん

L.67 で fs.rm() 実行時にエラーも考えられる?

init() (app.ts)
→ handleClasp() (app.ts)
→ ClaspHelper.create() (clasp-helper.ts called at app.ts L325)
→ ClaspHelper.clean() (clasp-helper.ts called at L111)
→ await fs.rm(path.join(rootDir, '.clasp.json'), {
      recursive: true,
      force: true,
    }); (L67)

でも force オプションを設定しているのでエラーは出ないはず。

こっこさんこっこさん

aside が実行している clasp のコマンドを実行してみた。
Apps Script API が有効化されていないのが問題だったぽい!

$ npx clasp create --type sheets --rootDir ./dist --title Untitled
Created new Google Sheet: https://drive.google.com/open?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
User has not enabled the Apps Script API. Enable it by visiting https://script.google.com/home/usersettings then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
こっこさんこっこさん

無事に初期化できた🙌

$ npx @google/aside init
✔ Project Title: … Untitled
✔ Generate package.json? … No / Yes
✔ Adding scripts...
✔ Saving package.json...
✔ Installing dependencies...
✔ Installing src template...
✔ Installing test template...
✔ Script ID (optional): … 
✔ Script ID for production environment (optional): … 
✔ Creating Untitled...

-> Google Sheets Link: https://drive.google.com/open?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-> Apps Script Link: https://script.google.com/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit
こっこさんこっこさん

マイドライブのルートディレクトリにプロジェクト名のシートが2つ作成されている

こっこさんこっこさん

Jest で次の関数をテストしたい

export function getSheetByName(
  name: string
): GoogleAppsScript.Spreadsheet.Sheet {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name);
  if (sheet === null)
    throw new SheetNotFoundError(
      `${name}」という名前のシートが見つかりませんでした!"`
    );
  return sheet;
}

GoogleAppsScript.SpreadsheetGoogleAppsScript.Spreadsheet.Sheet が interface として定義されており、どうモックすればよいかわからない

こっこさんこっこさん
describe('spreadsheet', () => {
  describe('getSheetByName', () => {
    it('シートが存在する場合', () => {
      const spreadsheetApp: MockProxy<GoogleAppsScript.Spreadsheet.SpreadsheetApp> =
        mock<GoogleAppsScript.Spreadsheet.SpreadsheetApp>();
      const spreadsheet: MockProxy<GoogleAppsScript.Spreadsheet.Spreadsheet> =
        mock<GoogleAppsScript.Spreadsheet.Spreadsheet>();
      const sheet: MockProxy<GoogleAppsScript.Spreadsheet.Sheet> =
        mock<GoogleAppsScript.Spreadsheet.Sheet>();

      spreadsheet.getSheetByName.mockReturnValue(sheet);
      spreadsheetApp.getActiveSpreadsheet.mockReturnValue(spreadsheet);
      jest
        .spyOn(SpreadsheetApp, 'getActiveSpreadsheet')
        .mockReturnValue(spreadsheet);

      getSheetByName('適当な文字列');
    });
  });
});
spreadsheet > getSheetByName > シートが存在する場合
-----
ReferenceError: SpreadsheetApp is not defined
こっこさんこっこさん
import { mock, MockProxy } from 'jest-mock-extended';
import { getSheetByName } from '../src/spreadsheet';

describe('spreadsheet', () => {
  describe('getSheetByName', () => {
    it('シートが存在する場合', () => {
      const spreadsheet: MockProxy<GoogleAppsScript.Spreadsheet.Spreadsheet> =
        mock<GoogleAppsScript.Spreadsheet.Spreadsheet>();
      const sheet: MockProxy<GoogleAppsScript.Spreadsheet.Sheet> =
        mock<GoogleAppsScript.Spreadsheet.Sheet>();

      spreadsheet.getSheetByName.mockReturnValue(sheet);
      SpreadsheetApp.getActiveSpreadsheet = jest
        .fn()
        .mockReturnValue(spreadsheet);

      getSheetByName('適当な文字列');
    });
  });
});
{
  "preset": "ts-jest",
  "testEnvironment": "node",
  "modulePathIgnorePatterns": ["<rootDir>/dist/", "<rootDir>/build/"],
  "testPathIgnorePatterns": ["<rootDir>/dist/", "<rootDir>/build/"],
  "moduleNameMapper": {
    "(.+)\\.js": "$1"
  },
  "globals": {
    "SpreadsheetApp": {}
  }
}