Contenu connexe
Similaire à Inside Frontend 2 #insideFE (20)
Inside Frontend 2 #insideFE
- 2. 1 <AboutMe
2 name="穴井宏幸"
3 handleName="@pirosikick"
4 titles={[
5 "ヤフー株式会社 第6・7代黒帯(JavaScript)",
6 "リッチラボ株式会社 エンジニア"
7 ]}
8 home="福岡"
9 />
- 3. 1 <about-me
2 name="穴井宏幸"
3 handle-name="@pirosikick"
4 :titles="[
5 'ヤフー株式会社 第6・7代黒帯(JavaScript)',
6 'リッチラボ株式会社 エンジニア'
7 ]"
8 home="福岡"
9 >
- 19. 1 // SomeComponent.spec.js
2 import test from 'ava';
3 import sinon from 'sinon';
4 import React from 'react';
5 import Enzyme, { shallow } from 'enzyme';
6 import Adapter from 'enzyme-adapter-react-16';
7
8 import SomeComponent from './SomeComponent';
9
10 // Reactのバージョンによってアダプタを変える
11 // 本来はavaのrequireで設定したほうがよい
12 Enzyme.configure({
13 adapter: new Adapter()
14 });
15
16 test('SomeComponent', t => {
17 // 描画したコンポーネントをラップしたものを返す
18 const wrapper = shallow(<SomeComponent />);
19
20 // ...テストケースを記述する...
21 });
- 20. 1 // CSSセレクタで探す
2 t.true(wrapper.find('.some-class').exists());
3 // コンポーネント名で探す
4 t.deepEqual(
5 wrapper.find('OtherComponent').at(0).props(),
6 { no: 1 }
7 );
8 // 子要素
9 t.true(wrapper.childAt(1).is('OtherComponent'));
10
11 // propsの再設定
12 const onClick = sinon.spy();
13 wrapper.setProps({ onClick });
14
15 // イベントの再現
16 wrapper.find('.button').simulate('click');
17 t.true(onClick.calledOnce);
- 21. 1 // someComponent.spec.js
2 import test from 'ava';
3 import { shallow } from '@vue/test-utils';
4
5 import someComponent from './someComponent.vue';
6 import otherComponent from './otherComponent.vue';
7
8 test('someComponent', t => {
9 // 描画したコンポーネントをラップしたものを返す
10 const wrapper = shallow(someComponent, {
11 propsData: {/* props */}
12 });
13
14 // ...テストケースを記述する...
15 });
- 22. 1 // CSSセレクタで探す
2 t.true(wrapper.find('.some-class').exists());
3 // コンポーネントで探す
4 t.deepEqual(
5 wrapper.findAll(otherComponent).at(0).props(),
6 { no: 1 }
7 );
8 // 子要素
9 // @vut/test-utilsは子要素の取得はできない?
10
11 // コンポーネントが保持している値の更新
12 wrapper.setProps({/* ... */});
13 wrapper.setData({/* ... */});
14
15 // イベントの再現
16 wrapper.find('.button').trigger('click');
17 // コンポーネントが$emit('clicked')したか
18 t.is(wrapper.emitted().clicked.length, 1);
- 29. 1 // CustomField.spec.js
2 import ...
3 import CustomField from './CustomField';
4
5 test('render ObjectField', t => {
6 const props = {
7 schema: {
8 type: "array",
9 items: {
10 image: { ... },
11 link: { ... }
12 }
13 },
14 formData: [ ... ]
15 };
16 const wrapper = shallow(<CustomField {...props} />);
17
18 t.true(wrapper.find('ObjectField').exists());
19 });
- 30. 1 // CustomField.js
2 import ObjectField from 'react-jsonschema-form/...';
3
4 export default function CustomField({
5 schema,
6 formData
7 }) {
8 return <ObjectField />;
9 }
10
- 31. 1 // アサーションを追加
2 // ObjectFieldに渡すpropsの検証
3 t.deepEqual(
4 wrapper.find('ObjectField').props(),
5 {
6 schema: props.schema.items,
7 formData: props.formData[0]
8 }
9 );
- 32. 1 // CustomField.js
2 import ObjectField from 'react-jsonschema-form/...';
3
4 export default function CustomField({
5 schema,
6 formData
7 }) {
8 return (
9 <ObjectField
10 schema={schema.items}
11 formData={formData[0]}
12 />
13 );
14 }
- 34. 1 // CustomField.spec.js
2 // テストケースを追加
3 test(`call onChange with new data
4 if ObjectField is changed`, t => {
5 const props = {
6 schema: { ... },
7 formData: [ ... ],
8 onChange: sinon.spy()
9 };
10 const wrapper = shallow(<CustomField {...props} />);
11
12 // ObjectField is changed
13 wrapper.find('ObjectField').props().onChange({ ... })
14
15 // call onChange with new data
16 t.true(props.onChange.calledOnce);
17 t.deepEqual(props.onChange.args[0], [ ... ]);
18 })
- 35. 1 // CustomField.js
2 import ObjectField from 'react-jsonschema-form/...';
3
4 export default function CustomField({
5 schema,
6 formData,
7 onChange
8 }) {
9 return (
10 <ObjectField
11 schema={schema.items}
12 formData={formData[0]}
13 onChange={data => {
14 onChange([data, ...formData.slice(1)]);
15 })
16 />
17 );
18 }
- 41. 1 // 実装
2 <Button data-testid="add-btn">追加</Button>
3 <Button data-testid="remove-btn">削除</Button>
4
5 // テスト
6 // セレクタや並び順では変更に弱い
7 // const rmeoveButton = wrapper.find('Button').at(1);
8 // カスタムデータ属性を使う
9 const removeButton
10 = wrapper.find('[data-testid="remove-btn"]');
11
12 // ヘルパーを作っておくと便利
13 const findByTestId = (wrapper, testid) =>
14 wrapper.find(`[data-testid="${testid}"]`);
- 43. 1 <!--
2 someComponent.vue
3 - otherComponentはslot(子要素)を受け取れる
4 -->
5 <template>
6 <other-component>
7 <div class="slot-contents"></div>
8 </other-component>
9 </template>
1 // someComponent.spec.js
2 // shallowでは子コンポーネントがスタブされる
3 const wrapper = shallow(someComponent);
4 // slotは描画されないので以下は失敗する
5 t.true(wrapper.find('.slot-contents').exists();
6
7 // mountならうまくいく
8 const wrapper = mount(someComponent);
9 t.true(wrapper.find('.slot-contents').exists();
- 44. 1 <!-- someComponent.vue -->
2 <template>
3 ...
4 <div class="mounted">
5 {{ isMounted ? 'mounted!' : 'not mounted' }}
6 </div>
7 </template>
8 <script>
9 export default {
10 data () {
11 return { isMounted: false }
12 },
13 mounted () {
14 // shallowでも呼ばれる
15 this.isMounted = true
16 }
17 }
18 </script>
- 45. 1 // someComponent.spec.js
2 // mountedは呼ばれるがtemplateは更新されていない
3 // 以下は全て通る
4 t.true(wrapper.vm.isMounted);
5 t.is(wrapper.find('.mounted').text(), 'not mounted');
6
7 // updateを実行するとtemplateに反映される
8 wrapper.update();
9 t.is(wrapper.find('.mounted').text(), 'mounted!!');