cordova+vue+ipadの嵌った内容

2018年のお仕事で対応した調査・対応の内容を備忘としてまとめてみようと思います。

# cordova+vue+ipadの開発の嵌った内容


### インテグレーションテストでもWebSQLからSQLのテストを実行したい

#### 動機
画面開発なら不要ですが、iosアプリとして作るので、ロジックとSQLを切り離すことはできません。  
従って、品質を担保するためにどうしてもSQLを回帰試験で実行したかった。

#### 対応（サンプル）

gitlab runnerのpipelineからテストを流したため、以下の3点を設定します。
runner設定：.gitlab-ci.yml
karma設定：karma.conf.js
テストコード：00Before.spec.js

* gitlab設定

社内で開発するため `proxy` 必須でした

```yml:.gitlab-ci.yml
# 環境設定
variables:
  CI: "true"
  HTTP_PROXY: "http://XXXX"
  HTTPS_PROXY: "http://XXXX"
  FTP_PROXY: "ftp://XXXX"
  NO_PROXY: "localhost"
  NODE_TLS_REJECT_UNAUTHORIZED: 0
  TZ: "Asia/Tokyo"
# キャッシュ設定
cache:
  untracked: true
  paths:
    - node_modules/
# まずはテストだけってことで
stages:
  - test
# テスト内容
npm_run_unit:
  stage: test
  script:
    - git config --global http.proxy XXX
    - git config --global https.proxy XXXX
    - git config --global http.sslVerify false
    - git config --global url."https://".insteadOf ssh://
    - npm -g config set XXXX
    - npm -g config set XXX
    - npm -g config set strict-ssl false
    - npm install -g monaca
    - monaca config proxy XXXX
    - npm install -g vue-cli
    - npm install
    - npm run unit
    - npm run lint
```


* アプリ設定

```js:karma.conf.js
// This is a karma config file. For more details see
//   http://karma-runner.github.io/0.13/config/configuration-file.html
// we are also using it with karma-webpack
//   https://github.com/webpack/karma-webpack

var webpackConfig = require('../../build/webpack.test.conf')

module.exports = function (config) {
  config.set({
    // to run in additional browsers:
    // 1. install corresponding karma launcher
    //    http://karma-runner.github.io/0.13/config/browsers.html
    // 2. add it to the `browsers` array below.
    browsers: [
      // 回帰試験なので、ChromeHeadlessです
      // 'Chrome',
      'ChromeHeadlessNoSandbox',
    ],
    customLaunchers: {
      ChromeHeadlessNoSandbox: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox']
      },
    },
    frameworks: ['mocha', 'sinon-chai'],
    reporters: ['spec', 'coverage'],
    files: ['./index.js'],
    preprocessors: {
      './index.js': ['webpack', 'sourcemap']
    },
    webpack: webpackConfig,
    webpackMiddleware: {
      noInfo: true
    },
    coverageReporter: {
      dir: './coverage',
      // 実行結果のレポートをコマンドライン上にだします
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text' },
        { type: 'text-summary' }
      ]
    },
    client: {
      mocha: {
        // dbのcreate文が失敗するため、延長設定
        timeout: 1000000
      }
    },
    browserDisconnectTolerance: 2,
    // browserがタイムアウトするため、延長設定
    browserNoActivityTimeout: 1000000
  })
}

```

* テストコード

苦し紛れ感ありますが、絶対に1番に流れるテストコード（00を先頭に付けて一番初めに流す）作ってそこで接続

```js:00Before.spec.js
import { createConnection, Connection, getConnection } from "typeorm"
import Logger from '@/util/logger'
const logger = new Logger();

describe('000 unit test start', () => {

  before(async function () {
    // connect
    const connection = connection = await createConnection(option).catch(error => this.logger.error(undefined, JSON.stringify(error)));
    // drop table
    const droptable_function = async function (manager) {
      // delete sql generate
      let sqls = await manager.query(`
            SELECT
                ' DELETE FROM	\"'  ||  NAME    ||  '\"'    AS  QUERY
            FROM
                SQLITE_MASTER
            WHERE
                TYPE    =   'table'
            AND NAME    !=  'sqlite_sequence'
            AND NAME    !=  '__WebKitDatabaseInfoTable__'`)
      for (let sql of sqls) {
        await manager.query(sql.QUERY)
      }
    }
    return await connection.transaction(droptable_function)
    // ddl execte(自動実行の場合は不要)
    await connection.synchronize()
    // 必要に応じてデータをDBに投入
  });

  beforeEach(function () {
  });


  after(function () {
  });

  afterEach(function () {
  });


  it('test000:DB connect', () => {
  })
})

```

### テストの結果が毎回変わる

#### 動機
テストが増えてくると何故かテスト結果が成功したりしなかったりしたため対応が必要になった

#### 対応
単にテストの追い越しが発生していただけでした。
テストケースが増えてくると根本的に対応することも難しくなり、後ろ向きに `wait` で対応しました

```js
/**
 * wait
 *
 * @export
 * @param {*} sec
 * @returns
 */
export function wait(sec) {
  return new Promise(resolve => setTimeout(resolve, sec * 1000));
}
```

### ErrorHandling

#### 動機

`Vue.config.errorHandler` に書けばいいのですが、`async` メソッドの場合errorHandlerがキャッチしてくれない為検討しました。
また、TypeORMのErrorHandlingも併せて検討が必要でした。

#### 対応
色々どうかと思おうのですが、mixinするutilを用意しasync用のfunctionを作りました。

```js
export const asyncFunction = async (blockFunction) => {
  try {
    return await blockFunction();
  } catch(error) {
    Vue.config.errorHandler(error, this, error.toString());
  }
}
```
Typeorm のマニュアルに従い、Custom loggerを作成し、ここからハンドリングしました

```js
  /**
   * Typeorm Custom logger
   *
   * @static
   * @memberof DbConnection
   * @see https://github.com/typeorm/typeorm/blob/master/docs/logging.md#using-custom-logger
   */
  static dbLogger = class DbLogger extends SimpleConsoleLogger {
    /**
     *Creates an instance of DbLogger.
     * @param {*} options
     */
    constructor(options) {
      super(options);
      this.logger = new Logger();
    }

    /**
     * Logs query and parameters used in it.
     *
     * @param {string} [query=""]
     * @param {Array} [parameters=[]]
     * @param {*} queryRunner
     */
    logQuery(query = "", parameters = [], queryRunner) {
      if (!process.env.DB_CONECT.logging) return;
      this.logger.debug(undefined, `[QueryDebug] query=${query}, params=${parameters}`)
    }

    /**
     * Logs query that is failed.
     *
     * @param {string} [error=""]
     * @param {string} [query=""]
     * @param {Array} [parameters=[]]
     * @param {QueryRunner} queryRunner
     */
    logQueryError(error = "", query = "", parameters = [], queryRunner) {
      if (!process.env.DB_CONECT.logging) return;

      this.logger.error(undefined, `[QueryError] error=${JSON.stringify(error, null, "  ")}`)
      if (error.stack) this.logger.error(undefined, `[QueryError] error=${JSON.stringify(error.stack)}`)
      this.logger.error(undefined, `[QueryError] query=${query}, params=${parameters}`)
    }

    /**
     * Logs query that is slow.
     *
     * @param {*} time
     * @param {string} [query=""]
     * @param {Array} [parameters=[]]
     * @param {QueryRunner} queryRunner
     */
    logQuerySlow(time, query = "", parameters = [], queryRunner) {
      if (!process.env.DB_CONECT.logging) return;
      this.logger.warn(undefined, `[QuerySlow] time=${time}`)
      this.logger.warn(undefined, `[QuerySlow] query=${query}, params=${parameters}`)
    }

    /**
     * Logs events from the schema build process.
     *
     * @param {string} [message=""]
     * @param {QueryRunner} queryRunner
     */
    logSchemaBuild(message = "", queryRunner) {
      if (!process.env.DB_CONECT.logging) return;
      this.logger.info(undefined, `[SchemaBuild] message=${message}`)
    }

    /**
     * Logs events from the migrations run process.
     *
     * @param {string} [message=""]
     * @param {QueryRunner} queryRunner
     */
    logMigration(message = "", queryRunner) {
      if (!process.env.DB_CONECT.logging) return;
      this.logger.info(undefined, `[Migration] message=${message}`)
    }

    /**
     * Perform logging using given logger, or by default to the console.
     * Log has its own level and message.
     *
     * @param {string} [level=""]
     * @param {string} [message=""]
     * @param {QueryRunner} queryRunner
     */
    log(level = "", message = "", queryRunner) {
      if (!process.env.DB_CONECT.logging) return;
      this.logger.info(undefined, `[Perform] message=${message}`)
    }
  }
```

### cordovaの起動処理をどこでHandlingするか？

#### 動機
`deviceready` しないとCordova pluginとか色々動かないので悩みました。

#### 対応
vueをcordova動いてから動かしました。

```js

/**
 * cordova環境判定
 * cordova環境かつ、物理デバイス接続要件を満たすか確認する
 *
 * @export
 * @returns boolean
 */
function isCordova() {
  if (!(typeof cordova === "undefined") && (cordova.platformId === 'ios' || cordova.platformId === 'android' || cordova.platformId === 'windows')) return true;
  return false;
}

function startUp() {
    new Vue({
      el: '#app',
      router: router,
      store,
      components: { App },
      template: '<App/>',
    })
}

if (isCordova()) {
  document.addEventListener("deviceready", startUp);
} else {
  startUp();
}
```


### cordovaの時はどのタイミングでDBのコネクション作ろうかな

#### 動機
`deviceready` しないとCordova pluginとか色々動かないのでDBに繋げず、DBに繋がないと画面が動かないので・・・となりました。

#### 対応
cordova起動後、vueが動く前に差し込みました。

```js
function startUp() {
  // onDeviceready()に各種初期化処理を仕込んで確実に終わってからvueを動かしました。
  onDeviceready().then(resolve => {
    new Vue({
      el: '#app',
      router: router,
      store,
      components: { App },
      template: '<App/>',
    })
  })
}
if (isCordova()) {
  document.addEventListener("deviceready", startUp);
} else {
  startUp();
}
```

### ipadのタッチが遅い

#### 動機
iapdタッチ後の動作がどうにも遅くて対応必須でした。

#### 対応
社内でipadのタッチには0.1秒のDelayがあると聞き（常識なんですね。。。はずかしいです）、対応してもらいました。

```js
// fastclick setting
FastClick.attach(document.body);
```

### ipadでアプリをバックグランドに移してから10分...アプリがフリーズするんですけど

#### 動機
アプリをバッググランドに置き、しばらくしてからフォアに戻すと画面が固まり、とても困りました。

#### 対応
iOSはバッググランドに置いてから時間がたつとメモリが最適化されるとのことで、この時壊れているんだろうという意見を聞いたため、
バッググランドに行くときに画面の情報をlocal storageに置き、復帰時に戻すようにしました。

以下の処理を `document.addEventListener("pause", pause);` の様に入れ込みました。

```js
/**
 * vue.$dataのdeepcopy
 *
 * @export
 * @param {*} data
 * @returns
 */
export function vueDataDeepCopy(data) {
  const GLOBAL_OBJ = [
    "__ob__"
  ];
  let propertyNames = Object.getOwnPropertyNames(data);
  let dataObject = {};

  // deep copy
  for (let property of propertyNames) {
    if (GLOBAL_OBJ.indexOf(property) >= 0) continue;
    try {
      dataObject[property] = JSON.parse(JSON.stringify(data[property]));
    } catch (error) {
      // エラーは無視
    }
  }
  return dataObject;
}
```

来年も頑張ります