InfoGrab DocsInfoGrab Docs

Vue.js 스타일 가이드

요약

GitLab은 기본적으로 eslint-vue-plugin의 plugin:vue/recommended를 사용합니다. Vue 템플릿에는 .vue를 사용합니다. Vue 앱에 전달되는 데이터를 명시적으로 정의합니다 코드베이스를 명시적이고 찾기 쉬우며 검색 가능한 상태로 유지하기 위해 이 경우에는 전개 연산자 사용을 권장하지 않습니다.

린팅#

GitLab은 기본적으로 eslint-vue-plugin의 plugin:vue/recommended를 사용합니다. 자세한 내용은 규칙 문서를 참고합니다.

기본 규칙#

  1. Vue 템플릿에는 .vue를 사용합니다. HAML에서 %template을 사용하지 않습니다.

  2. Vue 앱에 전달되는 데이터를 명시적으로 정의합니다

    // bad
    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      provide: {
        ...someDataset
      },
      props: {
        ...anotherDataset
      },
      render: createElement => createElement('component-name'),
    }));
    
    // good
    const { foobar, barfoo } = someDataset;
    const { foo, bar } = anotherDataset;
    
    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      provide: {
        foobar,
        barfoo
      },
      props: {
        foo,
        bar
      },
      render: createElement => createElement('component-name'),
    }));
    

    코드베이스를 명시적이고 찾기 쉬우며 검색 가능한 상태로 유지하기 위해 이 경우에는 전개 연산자 사용을 권장하지 않습니다. 이는 Vuex 상태 초기화처럼 위와 같은 이점이 있는 모든 곳에 적용됩니다. 위 패턴을 사용하면 인스턴스를 만들 때 스칼라가 아닌 값도 쉽게 파싱할 수 있습니다.

    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      props: {
        foo,
        bar: parseBoolean(bar)
      },
      render: createElement => createElement('component-name'),
    }));
    

템플릿 내 컴포넌트 사용#

  1. 템플릿에서 컴포넌트를 사용할 때는 다른 표기법보다 kebab-case 이름을 우선합니다

    // bad
    <MyComponent />
    
    // good
    <my-component />
    

컴포넌트 props#

  1. 범용 CSS 클래스를 전달하는 prop보다 의도를 설명하는 의미 있는 prop을 우선합니다

    // bad - hands styling ownership to every caller
    <user-name :username-css-classes="'gl-truncate'" />
    
    // good - the component owns its styling, callers express intent
    <user-name :truncate-username="true" />
    

    불리언 truncate-username 같은 의미 있는 prop은 스타일 소유권을 컴포넌트 안에 유지하므로, 호출하는 쪽이 내부 스타일을 바꿀 수 없고 컴포넌트는 사용되는 모든 곳에서 시각적으로 일관됩니다. 그러면 테스트도 내부 CSS 클래스가 아니라 공개 prop을 기준으로 검증할 수 있습니다.

번역 문자열#

  1. 번역 호출은 <template>에 직접 작성합니다. 템플릿과 메서드 양쪽에서 재사용하는 경우처럼 분명한 이유가 있을 때만 문자열을 $options.i18n으로 옮깁니다.

    // bad - a single-use string moved away from where it is rendered
    <gl-button>{{ $options.i18n.buttonLabel }}</gl-button>
    
    // good
    <gl-button>{{ s__('Plan|Button label') }}</gl-button>
    

    번역 헬퍼는 템플릿과 컴포넌트 JavaScript 양쪽에서 사용할 수 있고 gettext 추출기가 <template> 블록을 읽으므로, 인라인 문자열도 pot 파일에 반영됩니다. $options.i18n 사용이 정당한 경우의 전체 목록은 Vue 단일 파일 컴포넌트를 참고합니다. 외부화 헬퍼는 번역을 위한 페이지 준비를 참고합니다.

컴포넌트 name 속성#

모든 Vue 컴포넌트에는 name 속성이 있어야 합니다. 파일 이름에서 파생한 PascalCase를 사용합니다. 예를 들어 board_app.vue는 name: 'BoardApp'이 됩니다.

app.vue나 index.vue 같은 범용 파일 이름에는 디렉터리 경로의 컨텍스트를 접두사로 붙여 고유한 이름을 만듭니다. 예를 들어 admin/users/components/app.vue는 name: 'AdminUsersApp'이 됩니다.

EE 컴포넌트가 CE 컴포넌트와 이름이 같으면 EE 컴포넌트 이름에 EE 접미사를 붙입니다. 예를 들어 app/와 ee/app/ 양쪽에 BranchSelector 컴포넌트가 있다면 EE 버전은 name: 'BranchSelectorEE'를 사용합니다.

<style> 태그#

GitLab은 다음 몇 가지 이유로 Vue 컴포넌트에서 <style> 태그를 사용하지 않습니다.

  1. SCSS 변수와 믹스인, Tailwind CSS의 @apply 지시문을 사용할 수 없습니다.
  2. 이 스타일은 런타임에 삽입됩니다.
  3. CSS를 정의하는 다른 방법이 이미 몇 가지 있습니다.

<style> 태그 대신 Tailwind CSS 유틸리티 클래스나 페이지별 CSS를 사용합니다.

Vue 테스트#

Vue 컴포넌트를 효과적으로 테스트하려는 과정에서 여러 프로그래밍 패턴과 스타일 선호가 자리 잡았습니다. 다음 가이드는 그중 일부를 설명합니다. 엄격한 지침이라기보다는 GitLab이 Vue 테스트를 어떻게 작성하는지 이해하는 데 도움이 되는 제안과 좋은 사례 모음입니다.

컴포넌트 마운트#

일반적으로 Vue 컴포넌트를 테스트할 때는 모든 테스트 블록에서 컴포넌트를 "다시 마운트"해야 합니다.

이를 위해 다음을 수행합니다.

  1. 최상위 describe 블록 안에 변경 가능한 wrapper 변수를 만듭니다.
  2. mount 또는 shallowMount로 컴포넌트를 마운트합니다.
  3. 그 결과로 나온 Wrapper 인스턴스를 wrapper 변수에 다시 할당합니다.

전역의 변경 가능한 래퍼를 만들면 다음을 포함해 여러 이점이 있습니다.

  • 컴포넌트나 DOM 요소를 찾는 공통 함수를 정의할 수 있습니다.

    import MyComponent from '~/path/to/my_component.vue';
    describe('MyComponent', () => {
      let wrapper;
    
      // this can now be reused across tests
      const findMyComponent = wrapper.findComponent(MyComponent);
      // ...
    })
    
  • beforeEach 블록으로 컴포넌트를 마운트할 수 있습니다(자세한 내용은 createComponent 팩토리를 참고합니다).

  • 테스트 실행 후 enableAutoDestroy로 컴포넌트를 자동으로 제거할 수 있습니다. 이 설정은 shared_test_setup.js에 있습니다.

비동기 자식 컴포넌트#

shallowMount는 비동기 자식 컴포넌트의 컴포넌트 스텁을 만들지 않습니다. 비동기 자식 컴포넌트를 올바르게 스텁하려면 stubs 옵션을 사용합니다. 비동기 자식 컴포넌트에 name 옵션이 정의되어 있는지 확인합니다. 정의되어 있지 않으면 wrapper의 findComponent 메서드가 올바르게 동작하지 않을 수 있습니다.

createComponent 팩토리#

마운트 로직의 중복을 피하려면 각 테스트 블록에서 재사용할 수 있는 createComponent 팩토리 함수를 정의하는 것이 좋습니다. 이 함수는 mount와 shallowMount의 결과를 wrapper 변수에 다시 할당하는 클로저입니다.

import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';

describe('MyComponent', () => {
  // Initiate the "global" wrapper variable. This will be used throughout our test:
  let wrapper;

  // Define our `createComponent` factory:
  function createComponent() {
    // Mount component and reassign `wrapper`:
    wrapper = shallowMount(MyComponent);
  }

  it('mounts', () => {
    createComponent();

    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop defaults to `false`', () => {
    createComponent();

    expect(wrapper.props('isLoading')).toBe(false);
  });
})

마찬가지로 beforeEach 블록에서 createComponent를 호출해 테스트의 중복을 더 줄일 수 있습니다.

import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';

describe('MyComponent', () => {
  // Initiate the "global" wrapper variable. This will be used throughout our test
  let wrapper;

  // define our `createComponent` factory
  function createComponent() {
    // mount component and reassign `wrapper`
    wrapper = shallowMount(MyComponent);
  }

  beforeEach(() => {
    createComponent();
  });

  it('mounts', () => {
    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop defaults to `false`', () => {
    expect(wrapper.props('isLoading')).toBe(false);
  });
})

createComponent 모범 사례#

  1. 인수를 여러 개 두기보다 객체 인수 하나(또는 제한된 개수)를 사용하는 방안을 검토합니다. props 같은 공통 데이터에 단일 파라미터를 정의하는 것은 괜찮지만, JavaScript 스타일 가이드를 염두에 두고 파라미터 개수 제한을 지킵니다.

    // bad
    function createComponent(props, stubs, mountFn, foo) { }
    
    // good
    function createComponent({ props, stubs, mountFn, foo } = {}) { }
    
    // good
    function createComponent(props = {}, { stubs, mountFn, foo } = {}) { }
    
  2. 같은 테스트 묶음에서 mount와 shallowMount를 둘 다 사용해야 한다면, 컴포넌트를 마운트할 때 사용할 마운트 함수(mount 또는 shallowMount)를 받는 mountFn 파라미터를 createComponent 팩토리에 정의하면 유용합니다.

    import { shallowMount } from '@vue/test-utils';
    
    function createComponent({ mountFn = shallowMount } = {}) { }
    
  3. wrapper.findByTestId()를 노출하려면 mountExtended와 shallowMountExtended 헬퍼를 사용합니다.

    import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
    import { SomeComponent } from 'components/some_component.vue';
    
    let wrapper;
    
    const createWrapper = () => { wrapper = shallowMountExtended(SomeComponent); };
    const someButton = () => wrapper.findByTestId('someButtonTestId');
    
  4. data, methods 등 컴포넌트 내부를 확장하는 마운트 옵션은 사용하지 않습니다.

    import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
    import { SomeComponent } from 'components/some_component.vue';
    
    let wrapper;
    
    // bad :( - This circumvents the actual user interaction and couples the test to component internals.
    const createWrapper = ({ data }) => {
      wrapper = shallowMountExtended(SomeComponent, {
        data
      });
    };
    
    // good :) - Helpers like `clickShowButton` interact with the actual I/O of the component.
    const createWrapper = () => {
      wrapper = shallowMountExtended(SomeComponent);
    };
    const clickShowButton = () => {
      wrapper.findByTestId('show').trigger('click');
    }
    

컴포넌트 상태 설정#

  1. 가능하면 컴포넌트 상태를 설정할 때 setProps를 사용하지 않습니다. 대신 컴포넌트를 마운트할 때 컴포넌트의 propsData를 설정합니다.

    // bad
    wrapper = shallowMount(MyComponent);
    wrapper.setProps({
      myProp: 'my cool prop'
    });
    
    // good
    wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
    

    예외는 컴포넌트의 반응성을 어떤 방식으로든 테스트하려는 경우입니다. 예를 들어 특정 watcher가 실행된 뒤 컴포넌트의 출력을 테스트할 수 있습니다. 이런 동작을 테스트할 때 setProps를 사용하는 것은 괜찮습니다.

  2. 컴포넌트의 내부 상태를 설정해 컴포넌트의 실제 I/O 테스트를 우회하는 setData는 사용하지 않습니다. 대신 컴포넌트의 자식에서 이벤트를 발생시키거나 다른 부수 효과로 상태 변경을 유도합니다.

컴포넌트 상태 접근#

  1. props나 속성에 접근할 때는 wrapper.props().myProp이나 wrapper.vm.myProp보다 wrapper.props('myProp') 구문을 우선합니다.

    // good
    expect(wrapper.props().myProp).toBe(true);
    expect(wrapper.attributes().myAttr).toBe(true);
    
    // better
    expect(wrapper.props('myProp').toBe(true);
    expect(wrapper.attributes('myAttr')).toBe(true);
    
  2. 여러 prop을 검증할 때는 toEqual로 props() 객체의 깊은 동등성을 확인합니다.

    // good
    expect(wrapper.props('propA')).toBe('valueA');
    expect(wrapper.props('propB')).toBe('valueB');
    expect(wrapper.props('propC')).toBe('valueC');
    
    // better
    expect(wrapper.props()).toEqual({
      propA: 'valueA',
      propB: 'valueB',
      propC: 'valueC',
    });
    
  3. 일부 prop에만 관심이 있다면 toMatchObject를 사용할 수 있습니다. expect.objectContaining보다 toMatchObject를 우선합니다.

    // good
    expect(wrapper.props()).toEqual(expect.objectContaining({
      propA: 'valueA',
      propB: 'valueB',
    }));
    
    // better
    expect(wrapper.props()).toMatchObject({
      propA: 'valueA',
      propB: 'valueB',
    });
    

props 유효성 검사 테스트#

컴포넌트 props를 확인할 때는 assertProps 헬퍼를 사용합니다. props 유효성 검사 실패는 오류로 던져집니다.

import { assertProps } from 'helpers/assert_props'

// ...

expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()

Vue.js 스타일 가이드

GitLab v19.4
원문 보기

요약

GitLab은 기본적으로 eslint-vue-plugin의 plugin:vue/recommended를 사용합니다. Vue 템플릿에는 .vue를 사용합니다. Vue 앱에 전달되는 데이터를 명시적으로 정의합니다 코드베이스를 명시적이고 찾기 쉬우며 검색 가능한 상태로 유지하기 위해 이 경우에는 전개 연산자 사용을 권장하지 않습니다.

린팅#

GitLab은 기본적으로 eslint-vue-plugin의 plugin:vue/recommended를 사용합니다. 자세한 내용은 규칙 문서를 참고합니다.

기본 규칙#

  1. Vue 템플릿에는 .vue를 사용합니다. HAML에서 %template을 사용하지 않습니다.

  2. Vue 앱에 전달되는 데이터를 명시적으로 정의합니다

    // bad
    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      provide: {
        ...someDataset
      },
      props: {
        ...anotherDataset
      },
      render: createElement => createElement('component-name'),
    }));
    
    // good
    const { foobar, barfoo } = someDataset;
    const { foo, bar } = anotherDataset;
    
    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      provide: {
        foobar,
        barfoo
      },
      props: {
        foo,
        bar
      },
      render: createElement => createElement('component-name'),
    }));
    

    코드베이스를 명시적이고 찾기 쉬우며 검색 가능한 상태로 유지하기 위해 이 경우에는 전개 연산자 사용을 권장하지 않습니다. 이는 Vuex 상태 초기화처럼 위와 같은 이점이 있는 모든 곳에 적용됩니다. 위 패턴을 사용하면 인스턴스를 만들 때 스칼라가 아닌 값도 쉽게 파싱할 수 있습니다.

    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      props: {
        foo,
        bar: parseBoolean(bar)
      },
      render: createElement => createElement('component-name'),
    }));
    

템플릿 내 컴포넌트 사용#

  1. 템플릿에서 컴포넌트를 사용할 때는 다른 표기법보다 kebab-case 이름을 우선합니다

    // bad
    <MyComponent />
    
    // good
    <my-component />
    

컴포넌트 props#

  1. 범용 CSS 클래스를 전달하는 prop보다 의도를 설명하는 의미 있는 prop을 우선합니다

    // bad - hands styling ownership to every caller
    <user-name :username-css-classes="'gl-truncate'" />
    
    // good - the component owns its styling, callers express intent
    <user-name :truncate-username="true" />
    

    불리언 truncate-username 같은 의미 있는 prop은 스타일 소유권을 컴포넌트 안에 유지하므로, 호출하는 쪽이 내부 스타일을 바꿀 수 없고 컴포넌트는 사용되는 모든 곳에서 시각적으로 일관됩니다. 그러면 테스트도 내부 CSS 클래스가 아니라 공개 prop을 기준으로 검증할 수 있습니다.

번역 문자열#

  1. 번역 호출은 <template>에 직접 작성합니다. 템플릿과 메서드 양쪽에서 재사용하는 경우처럼 분명한 이유가 있을 때만 문자열을 $options.i18n으로 옮깁니다.

    // bad - a single-use string moved away from where it is rendered
    <gl-button>{{ $options.i18n.buttonLabel }}</gl-button>
    
    // good
    <gl-button>{{ s__('Plan|Button label') }}</gl-button>
    

    번역 헬퍼는 템플릿과 컴포넌트 JavaScript 양쪽에서 사용할 수 있고 gettext 추출기가 <template> 블록을 읽으므로, 인라인 문자열도 pot 파일에 반영됩니다. $options.i18n 사용이 정당한 경우의 전체 목록은 Vue 단일 파일 컴포넌트를 참고합니다. 외부화 헬퍼는 번역을 위한 페이지 준비를 참고합니다.

컴포넌트 name 속성#

모든 Vue 컴포넌트에는 name 속성이 있어야 합니다. 파일 이름에서 파생한 PascalCase를 사용합니다. 예를 들어 board_app.vue는 name: 'BoardApp'이 됩니다.

app.vue나 index.vue 같은 범용 파일 이름에는 디렉터리 경로의 컨텍스트를 접두사로 붙여 고유한 이름을 만듭니다. 예를 들어 admin/users/components/app.vue는 name: 'AdminUsersApp'이 됩니다.

EE 컴포넌트가 CE 컴포넌트와 이름이 같으면 EE 컴포넌트 이름에 EE 접미사를 붙입니다. 예를 들어 app/와 ee/app/ 양쪽에 BranchSelector 컴포넌트가 있다면 EE 버전은 name: 'BranchSelectorEE'를 사용합니다.

<style> 태그#

GitLab은 다음 몇 가지 이유로 Vue 컴포넌트에서 <style> 태그를 사용하지 않습니다.

  1. SCSS 변수와 믹스인, Tailwind CSS의 @apply 지시문을 사용할 수 없습니다.
  2. 이 스타일은 런타임에 삽입됩니다.
  3. CSS를 정의하는 다른 방법이 이미 몇 가지 있습니다.

<style> 태그 대신 Tailwind CSS 유틸리티 클래스나 페이지별 CSS를 사용합니다.

Vue 테스트#

Vue 컴포넌트를 효과적으로 테스트하려는 과정에서 여러 프로그래밍 패턴과 스타일 선호가 자리 잡았습니다. 다음 가이드는 그중 일부를 설명합니다. 엄격한 지침이라기보다는 GitLab이 Vue 테스트를 어떻게 작성하는지 이해하는 데 도움이 되는 제안과 좋은 사례 모음입니다.

컴포넌트 마운트#

일반적으로 Vue 컴포넌트를 테스트할 때는 모든 테스트 블록에서 컴포넌트를 "다시 마운트"해야 합니다.

이를 위해 다음을 수행합니다.

  1. 최상위 describe 블록 안에 변경 가능한 wrapper 변수를 만듭니다.
  2. mount 또는 shallowMount로 컴포넌트를 마운트합니다.
  3. 그 결과로 나온 Wrapper 인스턴스를 wrapper 변수에 다시 할당합니다.

전역의 변경 가능한 래퍼를 만들면 다음을 포함해 여러 이점이 있습니다.

  • 컴포넌트나 DOM 요소를 찾는 공통 함수를 정의할 수 있습니다.

    import MyComponent from '~/path/to/my_component.vue';
    describe('MyComponent', () => {
      let wrapper;
    
      // this can now be reused across tests
      const findMyComponent = wrapper.findComponent(MyComponent);
      // ...
    })
    
  • beforeEach 블록으로 컴포넌트를 마운트할 수 있습니다(자세한 내용은 createComponent 팩토리를 참고합니다).

  • 테스트 실행 후 enableAutoDestroy로 컴포넌트를 자동으로 제거할 수 있습니다. 이 설정은 shared_test_setup.js에 있습니다.

비동기 자식 컴포넌트#

shallowMount는 비동기 자식 컴포넌트의 컴포넌트 스텁을 만들지 않습니다. 비동기 자식 컴포넌트를 올바르게 스텁하려면 stubs 옵션을 사용합니다. 비동기 자식 컴포넌트에 name 옵션이 정의되어 있는지 확인합니다. 정의되어 있지 않으면 wrapper의 findComponent 메서드가 올바르게 동작하지 않을 수 있습니다.

createComponent 팩토리#

마운트 로직의 중복을 피하려면 각 테스트 블록에서 재사용할 수 있는 createComponent 팩토리 함수를 정의하는 것이 좋습니다. 이 함수는 mount와 shallowMount의 결과를 wrapper 변수에 다시 할당하는 클로저입니다.

import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';

describe('MyComponent', () => {
  // Initiate the "global" wrapper variable. This will be used throughout our test:
  let wrapper;

  // Define our `createComponent` factory:
  function createComponent() {
    // Mount component and reassign `wrapper`:
    wrapper = shallowMount(MyComponent);
  }

  it('mounts', () => {
    createComponent();

    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop defaults to `false`', () => {
    createComponent();

    expect(wrapper.props('isLoading')).toBe(false);
  });
})

마찬가지로 beforeEach 블록에서 createComponent를 호출해 테스트의 중복을 더 줄일 수 있습니다.

import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';

describe('MyComponent', () => {
  // Initiate the "global" wrapper variable. This will be used throughout our test
  let wrapper;

  // define our `createComponent` factory
  function createComponent() {
    // mount component and reassign `wrapper`
    wrapper = shallowMount(MyComponent);
  }

  beforeEach(() => {
    createComponent();
  });

  it('mounts', () => {
    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop defaults to `false`', () => {
    expect(wrapper.props('isLoading')).toBe(false);
  });
})

createComponent 모범 사례#

  1. 인수를 여러 개 두기보다 객체 인수 하나(또는 제한된 개수)를 사용하는 방안을 검토합니다. props 같은 공통 데이터에 단일 파라미터를 정의하는 것은 괜찮지만, JavaScript 스타일 가이드를 염두에 두고 파라미터 개수 제한을 지킵니다.

    // bad
    function createComponent(props, stubs, mountFn, foo) { }
    
    // good
    function createComponent({ props, stubs, mountFn, foo } = {}) { }
    
    // good
    function createComponent(props = {}, { stubs, mountFn, foo } = {}) { }
    
  2. 같은 테스트 묶음에서 mount와 shallowMount를 둘 다 사용해야 한다면, 컴포넌트를 마운트할 때 사용할 마운트 함수(mount 또는 shallowMount)를 받는 mountFn 파라미터를 createComponent 팩토리에 정의하면 유용합니다.

    import { shallowMount } from '@vue/test-utils';
    
    function createComponent({ mountFn = shallowMount } = {}) { }
    
  3. wrapper.findByTestId()를 노출하려면 mountExtended와 shallowMountExtended 헬퍼를 사용합니다.

    import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
    import { SomeComponent } from 'components/some_component.vue';
    
    let wrapper;
    
    const createWrapper = () => { wrapper = shallowMountExtended(SomeComponent); };
    const someButton = () => wrapper.findByTestId('someButtonTestId');
    
  4. data, methods 등 컴포넌트 내부를 확장하는 마운트 옵션은 사용하지 않습니다.

    import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
    import { SomeComponent } from 'components/some_component.vue';
    
    let wrapper;
    
    // bad :( - This circumvents the actual user interaction and couples the test to component internals.
    const createWrapper = ({ data }) => {
      wrapper = shallowMountExtended(SomeComponent, {
        data
      });
    };
    
    // good :) - Helpers like `clickShowButton` interact with the actual I/O of the component.
    const createWrapper = () => {
      wrapper = shallowMountExtended(SomeComponent);
    };
    const clickShowButton = () => {
      wrapper.findByTestId('show').trigger('click');
    }
    

컴포넌트 상태 설정#

  1. 가능하면 컴포넌트 상태를 설정할 때 setProps를 사용하지 않습니다. 대신 컴포넌트를 마운트할 때 컴포넌트의 propsData를 설정합니다.

    // bad
    wrapper = shallowMount(MyComponent);
    wrapper.setProps({
      myProp: 'my cool prop'
    });
    
    // good
    wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
    

    예외는 컴포넌트의 반응성을 어떤 방식으로든 테스트하려는 경우입니다. 예를 들어 특정 watcher가 실행된 뒤 컴포넌트의 출력을 테스트할 수 있습니다. 이런 동작을 테스트할 때 setProps를 사용하는 것은 괜찮습니다.

  2. 컴포넌트의 내부 상태를 설정해 컴포넌트의 실제 I/O 테스트를 우회하는 setData는 사용하지 않습니다. 대신 컴포넌트의 자식에서 이벤트를 발생시키거나 다른 부수 효과로 상태 변경을 유도합니다.

컴포넌트 상태 접근#

  1. props나 속성에 접근할 때는 wrapper.props().myProp이나 wrapper.vm.myProp보다 wrapper.props('myProp') 구문을 우선합니다.

    // good
    expect(wrapper.props().myProp).toBe(true);
    expect(wrapper.attributes().myAttr).toBe(true);
    
    // better
    expect(wrapper.props('myProp').toBe(true);
    expect(wrapper.attributes('myAttr')).toBe(true);
    
  2. 여러 prop을 검증할 때는 toEqual로 props() 객체의 깊은 동등성을 확인합니다.

    // good
    expect(wrapper.props('propA')).toBe('valueA');
    expect(wrapper.props('propB')).toBe('valueB');
    expect(wrapper.props('propC')).toBe('valueC');
    
    // better
    expect(wrapper.props()).toEqual({
      propA: 'valueA',
      propB: 'valueB',
      propC: 'valueC',
    });
    
  3. 일부 prop에만 관심이 있다면 toMatchObject를 사용할 수 있습니다. expect.objectContaining보다 toMatchObject를 우선합니다.

    // good
    expect(wrapper.props()).toEqual(expect.objectContaining({
      propA: 'valueA',
      propB: 'valueB',
    }));
    
    // better
    expect(wrapper.props()).toMatchObject({
      propA: 'valueA',
      propB: 'valueB',
    });
    

props 유효성 검사 테스트#

컴포넌트 props를 확인할 때는 assertProps 헬퍼를 사용합니다. props 유효성 검사 실패는 오류로 던져집니다.

import { assertProps } from 'helpers/assert_props'

// ...

expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()