NISHIO Hirokazu[Translate]
2021-07-14Movidea開発日記
今日の一枚

Cypress、containsはDOMが選択されるが型がundefined
ts
cy.get("div[data-testid='2']").should((x) => {}); // x is JQuery<HTMLElement> cy.contains("A B").should((x) => {}); // x is undefined

これはDOMを取得する方法としての実装とアサーションとしての実装の両方があるせい。
こんな感じになってるが、cyが Chainable<undefined> だから Subject=undefined になってしまう。設計が悪い、名前を分けるべき。
cypress.d.ts
contains(content: string | number | RegExp, options?: Partial<Loggable & Timeoutable & CaseMatchable & Shadow>): Chainable<Subject> contains<E extends Node = HTMLElement>(content: string | number | RegExp): Chainable<JQuery<E>> ... interface cy extends Chainable<undefined> {}

それはさておき自分の実装としてはdata-testidを付けてあるので、それを使って選択するのをカスタムコマンドにしてやりやすくしようと考えた。

support/index.ts
declare global { namespace Cypress { interface Chainable { ... testid(testid: string): Chainable<Element>; hasPosition(x: number, y: number): Chainable<Element>; } } } Cypress.Commands.add("testid", (testid: string) => { return cy.get(`*[data-testid='${testid}']`); }); Cypress.Commands.add( "hasPosition", { prevSubject: true, }, (subject: Cypress.Chainable<Element>, x: number, y: number) => { const cr = subject[0].getBoundingClientRect(); expect(cr.x).equal(x); expect(cr.y).equal(y); return subject; } );

これで
cy.testid("1").hasPosition(159, 174);
と書けるようになった。
しかしこれはリトライが行われない。

実際のところCustom Commands | Cypress Documentationには子コマンドの作り方は書いてあるが例ではアサーションをしていない。

Assertions | Cypress Documentationを見るとChaiにアサーションを追加しろと書いてある。
Adding Chai Assertionsを参考に書いてみた。
support/index.ts
chai.use((_chai, utils) => { function hasPosition(options) { const [x, y] = options; const cr = this._obj[0].getBoundingClientRect(); this.assert( cr.x === x, `expected x:${cr.x} is ${x}`, `expected x:${cr.x} is not ${x}`, cr.x ); this.assert( cr.y === y, `expected y:${cr.y} is ${y}`, `expected y:${cr.y} is not ${y}`, cr.y ); } _chai.Assertion.addMethod("hasPosition", hasPosition); });
これで
cy.testid("1").should("hasPosition", [159, 174]);
と書けるようになった。
shouldだったらhaveだなと思うがそういうことを言い出すとそもそもChai的には
cy.testid("1").has.position([159, 174]);
と書けるべきなのでは、みたいな気持ちになってきて、これはCypressとChaiの境目あたりにあって話がややこしいので保留した。

下記のテストが簡潔に書けるようになった
test.ts
// before cy.get("div[data-testid='1']").should((x) => { expect(x[0].getBoundingClientRect().x).equal(x1); expect(x[0].getBoundingClientRect().y).equal(y1); }); // after cy.testid("1").should("hasPosition", [x1, y1]);

だいぶカスタムコマンドに慣れてきて、そもそもupdateGlobalもよく使うからカスタムコマンドにしたらいいなと気づいた
support/index.ts
Cypress.Commands.add("updateGlobal", (callback: (g: State) => void) => { return cy.movidea((movidea) => { movidea.updateGlobal(callback); }); });

下記のようにいちいちmovideaを介さなくても状態更新ができるようになった
test.ts
// before cy.movidea((movidea) => { movidea.updateGlobal((g) => { g.itemStore["1"].position = [dx, 0]; }); }); // after cy.updateGlobal((g) => { g.itemStore["1"].position = [dx, 0]; });

「アイテム3番(付箋B)を動かしたらアイテム1番(グループ)の位置はどこどこになるべき」というテスト。(アイテムIDもわかりやすい名前にした方がよかったな)
test.ts
cy.updateGlobal((g) => { g.itemStore["3"].position = [-100, 0]; }); cy.testid("1").should("hasPosition", [55, 170]);

で、この状態からグループを閉じると場所がずれる。次はこれを直していこう。
test.ts
cy.updateGlobal((g) => { (g.itemStore["1"] as GroupItem).isOpen = false; });

Cypress、テストの各段階のスナップショットが見れるので「微妙に4ピクセルずれるバグ」を確認することができた、便利

ここいらでもう一度RegroupからエクスポートされたJSONを表示してみよう。なるほどここまでちゃんとなると何がバグの原因かわかりやすい、グループの中のグループのバウンディングボックスが平行移動の情報を失ってるのが原因だな。

直した。色々なところに波及するが、テストケースがあるので動作確認しやすくて良い。ちゃんとした見栄えになった。
Regroupでの見え方 from 2021-07-01#60df19d4aff09e0000c6d717
ほぼ同じ!
今までのテストケースも全部成功する。


明日はメニューやダイアログ周りをやる
OOUIを読み返した
OOUIの解説を読み直してたんだけど「新規作成」も「インポート」もタスク指向なんだよなぁ
オブジェクト指向UIにするとしたらどうなるのだろう
既存のマップを開くURLではないものにアクセスする時点で「新規作成」なのは当たり前だし、新規作成した後コンテンツを入れないということも考えにくいのだからそもそも最初からインポートウィザードが開いているべきなのかな


インポートではなく「付箋の追加」ではないか
URLからインポートするときのUIは?


"Engineer's way of creating knowledge" the English version of my book is now available on [Engineer's way of creating knowledge]

(C)NISHIO Hirokazu / Converted from [Scrapbox] at [Edit]