かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

RSpec Capybara name / id のない input の fill_in にハマる

プロダクトの施策であるフォームを react component に置き換えた所、元々あった feature spec で fill_in していた部分が Capybara::ElementNotFound: で落ちるようになってしまいました。

fill_in は id か name が無いと選択できない?

新しくした React component の JS でフォーム送信をしていたので、フォーム内の input タグに id / name 属性が無かったので、feature spec を次のような感じに置き換えていました。

fill_in 'input[type="email"]', with: user.email

=> これはエラーになる。Capybara::ElementNotFound: Unable to find field "input[type=\"email\"]" that is not disabled within #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/DIV[9]/DIV[1]">

#fill_in([locator], with: , **options) ⇒ Capybara::Node::Element
Locate a text field or text area and fill it in with the given text. The field can be found via its name, id, test_id attribute, placeholder, or label text. If no locator is provided this will operate on self or a descendant.
cf. https://rubydoc.info/github/jnicklas/capybara/master/Capybara%2FNode%2FActions:fill_in

ドキュメントを見た感じ name, id, test_id, attribute, placeholder, label で見つけることはできるようです。
つまりCSSセレクタ的な input[type="email"].inputField みたいなのはダメって事のようです。(単一なものとして取得しづらいからでしょうか…?)

id / name の無い input を Capybara で操作するには findset を使う

今回は fill_in で取得できる id や 属性 が無く、React component は色々な所で使いまわしていたのでテストのために属性を追加したくはないという感じでした。
このような場合は find で要素を見つけてきて set で値をセットすれば OKトノコト。

find('input[type="email"]').set(user.email)

先程のテストをこのように書き換えたら無事テストが実行できるようになりました。₍ᐢ •̥ ̫ • ᐢ₎👌 やったね!  

docker-compose 上で動いてるアプリの RSpec 回そうとしたら TimeoutError になって辛い… (怒りの WIP commit でCIぶん回しました…)


[参考]

カピバラ

カピバラ

  • 作者:渡辺克仁
  • 出版社/メーカー: 東京書籍
  • 発売日: 2015/09/04
  • メディア: Kindle

TypeScript type を & でいい感じにマージしたい。

TypeScript はちゃんと学習していないのですが、関わっているプロダクトが ts, tsx なので触る時に雰囲気で使っています。

今回はあるコンポーネントをラップするコンポーネントを作成していて、バックエンドから渡される値が、ラップするコンポーネントに定義されているPropsと異なっていて、ラップする側で足りない場合はデフォルト値を設定したいという感じでした。
👇こんなイメージ

// ラップするコンポーネントの Props の型
type ModalProps = {
  modalMode: string;
  ...
  returnPath: string;
};

// ラップするコンポーネントの Props の型をこうしたい
type ModalButtonProps = {
  label?: string;
  className?: string;
  // 以下ラップするコンポーネントの Props
  modalMode: string;
  …
  returnPath?: string;
}

ラップするコンポーネントの type を export して returnPath の type を上書きする感じでマージしたい
type {} で囲われてるし JS のオブジェクトのマージの感覚で次のように型定義をしたのですが意図したとおりにはなりませんでした。

import { ModalProps } from './Modal';

type ModalButtonProps = ModalProps & {
  label?: string;
  className?: string;
  returnPath?: string; // 上書きしたい type
}

後に & で繋げば、後に書かれている方が優先されるのかな?と思ったのですが、returnPath が無い Props を受け取ると returnPath: string だと lint に怒られてしまいました。type ModalButtonProps = {...} & ModalProps と逆にしてみても同じエラーに…

& はマージではなく intersection型

チームの人に訊いたら & はマージではなく intersection という型で「積」だから A & BB & A も同じ結果になると教えてもらいました。これだけでは理解しきれなかったので調べてみた所 intersection というのは「 A かつ B 」という意味ということでした。
(画像で思考するタイプなのでこの図をみてやっと理解できました)

つまり先の例ではで同じキーである returnPath の型は「returnPath: string; かつ returnPath?: string;」という状態を指し、両方を満たすケースは 「returnPath: string;」の時だけとなるので、returnPath を渡さないとこの条件に合わないのでエラーとなっていた。という事のようでした!

完 全 理 解 !

Omit で 特定の型を取り除く

Omit

type Omit<T, K extends keyof any>
型 T の中から、キー名が K に当てはまるプロパティを除外した新しい型を返します。なお、Pick とは異なり K には T のキー名以外を指定することができます。
cf. TypeScript特有の組み込み型関数 - log.pocka.io / Announcing TypeScript 3.5 | TypeScript

今回は元々ある A: stringA?:string にしたい。他にも型を追加したいという感じだったので、Omit を使って元の型から A: string を除き & でくっつけるというのが良さそうです。

型の指定を次のように変更したら意図したとおりになりました

// Modal.tsx
export type ModalProps = {
  modalMode: string;
  returnPath: string;
};

// Button.tsx
import { ModalProps } from './Modal';

type ModalButtonProps = Omit<ModalProps, 'returnPath'> & {
  label?: string;
  className?: string;
  returnPath?: string;
}

ModalButtonProps は次のような型として解釈されます

type ModalButtonProps = {
  modalMode: string;
  label?: string;
  className?: string;
  returnPath?: string;
}

タイトルの マージ というのとは少し違いますが、今回はこの方法でマージっぽく型をつくる事ができました。

感想

学習時間を取れずプロダクトにあるコードを実践で見様見真似で触っていたので、&| の意味を勘違いしていたことがわかり、その意味を調べて知ることができてよかったです。

が、他にも typeinterface や色々な指定方法があって雰囲気で使うには JavaScript でどう作れば良いのかは分かってるけど、TypeScript にしたらプロダクトの本質ではない型宣言が知識不足でハマってしまい時間がかかってしまう状態というのが非常にストレスなので、そろそろちゃんと学習せねばという気になりかけています。自分の能力不足だと認識している問題ははストレス大きいですからね…


[参考]

実践TypeScript ~	BFFとNext.js&Nuxt.jsの型定義~

実践TypeScript ~ BFFとNext.js&Nuxt.jsの型定義~

Git リモートのブランチが先に進んでpushできなくなっているのをどうにかしたい。

複数人で同じブランチに手を入れていて、手元のブランチで変更をコミットして push しようとしたらリモートのブランチが force push とかで先に進んでそのまま push できない状態になっている時の対処方法のメモ

--rebase オプションを付けて pull する

$ git pull --rebase origin <branch_name>

--rebase オプションを付けてpullするとリモートのブランチを取り込んで rebase できる。
つまり、これが
Git リモートブランチが rebase などでそのままpushできなくなっている状態
👇こうなる
git pull --rebase でリモートで rebase した状態にできる

rebase なのでコンフリクトすることもあるけど、手元のブランチ名を変えて、リモートブランチを checkout し直して必要なコミットを cherry-pick するよりだいぶ楽です。( pull --rebase 知るまでずっとこうしてた… )

おまけ git fetch --prune でリモートで消されたブランチをローカルに反映する

--prune オプションを付けて fetch すると、リモートで削除されたブランチの情報をローカルに反映してくれる。
リモートで消えてるブランチの情報が残り続けてると無駄にデータ容量とってしまうのでローカルの git の容量削減に。

$ git fetch --prune

.gitconfig に設定すれば fetch 時に常に --prune を指定してくれるようにもできるっぽい

$ git config --global fetch.prune true

[参考]

入門git

入門git