テストコードをTSにするかJSにするかという話、とりあえずデフォルトのJSでやってたけどimmerで更新してCypressでテストで、型で補完が効かないせいで早速Typoしたので、やっぱりCypressTypeScript化は必須だという結論になった。

image

TypeScript | Cypress Documentationを参考にTypeScript化する。

下記のようなことを書いてある記述を見かけたがこれはcypressディレクトリの中にtsconfigを置く場合は適切ではない、そうでない場合もカスタムコマンドのことを無視してる tsconfig.json

"include": [
  "cypress/integration/*.ts",
  "cypress/integration/**/*.ts",
]

相対パスなのでこんな感じ(full) tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es5",
      "dom"
    ],
    "types": [
      "cypress"
    ]
  },
  "include": [
    "*.ts",
    "**/*.ts",
    "../src/*.ts",
    "../src/**/*.ts",
  ]
}

*.tsxをインポートしようとすると—jsxが必要というエラーになるが、そもそも今回のユースケースでtsxをインポートできる必要があるか不明だったのでシンプルに保つことにした。

import { State } from "reactn/default";して型宣言をすればちゃんとTypoが警告されるようになった image

ところで@ts-ignoreしてる件、 cy.window().its("movidea").then((movidea) => { ... } だと引数はanyで、 cy.get("@movidea").then((movidea) => { ... } だと JQuery<HTMLElement> になる。

いちいちmovideaをキャストするのは不便。 カスタムコマンドを作るか

ここでサンプルの通りにしたつもりでType 'Chainable' is not generic.とかProperty 'movidea' does not exist on type 'cy & EventEmitter'.とかになったりすったもんだがあったが最終的にこれでOK cypress/support/index.ts

/// <reference types="cypress" />
import { TMovidea } from "../../src/exposeGlobal";
 
declare global {
  namespace Cypress {
    interface Chainable {
      movidea(callback: (movidea: TMovidea) => void): Chainable;
    }
  }
}
 
Cypress.Commands.add("movidea", (callback: (movidea: TMovidea) => void) => {
  return cy.window().its("movidea").then(callback);
});

公式ドキュメントに下記のように書いてあったんだけど、これエラーにならない? ts

declare namespace Cypress {
  interface Chainable {
    /**
     * Custom command to select DOM element by data-cy attribute.
     * @example cy.dataCy('greeting')
     */
    dataCy(value: string): Chainable<Element>
  }
}

Cypressのソースを確認したら ts

declare global {
  namespace Cypress {
    // TODO: Why is Subject unused?
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    interface Chainable<Subject = any> {
      ...

ってなってるので、globalを補ったら期待通りに動くようになった。

最終的にテストコード中でこう書けるようになった。破壊的に更新してるように見えるけどimmerを使って非破壊的更新がされてて、ちゃんとReactのフックによる再描画が走る。 test.ts

    cy.movidea((movidea) => {
      movidea.updateGlobal((g) => {
        g.itemStore["1"].position = [100, 0];
      });
    });