[Copy Stack] storybook에서 스토어 mocking하기

dev2820·2022년 12월 28일
0

프로젝트: Copy Stack

목록 보기
19/28

lit-element에서 스토어를 사용하는 경우 스토리북에서 스토어를 mocking하는 방법입니다.

storybook을 쓰다가 특정 상황에 컴포넌트가 어떻게 보여지는지 작성하고 싶을 때가 있습니다. 보통은 args 파라미터값을 줘서 element의 attribute를 준다던가 slot에 들어갈 값을 줘서 해결할 수 있습니다.

// FilledCard.stories.ts
// 불필요한 영역은 생략
...
type Args = {
  content: string | TemplateResult<1>;
};

/**
 * 아래는 slot을 이용하는 lit-element의 Template 코드,
 * 아래 방식이 아니어도 attribute를 주거나 외부를 감싸는 
 * element를 별도로 작성해 실제 앱과 같은 환경을 구성할 수 있다. 
 */
const Template: Story<Args> = (args: Args) =>
  html`<filled-card>${args.content}</filled-card>`;

export const Empty = Template.bind({});
Empty.args = {
  content: "",
};

export const Text = Template.bind({});
Text.args = {
  content: "Lorem Ipsum",
};

export const Image = Template.bind({});
Image.args = {
  content: html`<img src="${noImageUrl}" />`,
};

하지만 스토어에서 값을 읽는 경우 스토리북에서 args를 통해 조절할 수 없습니다. 이 경우 스토어를 mocking해서 스토어가 특정 데이터를 갖고 있는 경우를 재현할 수 있습니다.

예시

예를 들어 아래처럼 Channel 클래스에서 copyList 속성값을 읽는 경우입니다.

// CopyList.ts
import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";

/**
 * Channel 클래스를 import, 여기선 Channel 클래스를 스토어처럼 사용하지만 본인의 개발환경에 맞게 import하면 됩니다.
 예를 들면 pinia를 쓰는 경우 
 import { useCopyStore } from "@/store/copy"
 */
import Channel from "@/classes/Channel";

@customElement("copy-list")
export default class CopyList extends LitElement {
  @state()
  copyList: Copy[] = [];

  constructor() {
    super();
    this.#created();
  }
  
  render() {
    return html`
      <ul class="copy-list">
        ${this.copyList.map(
          (copy) =>
            html` <li>
              <filled-card class="card">
                <div>
                  ${copy}
                </div>
              </filled-card>
            </li>`
        )}
      </ul>
    `;
  }

  async #created() {
    this.copyChannel = new Channel();

    this.copyList = [...this.copyChannel.copyList];
  }
}

위 컴포넌트는 copyChannel로부터 읽은 copyList 배열을 통해 여러 <li> 요소를 랜더링하고 있습니다.

해결

스토리 작성하기

// CopyList.stories.ts
import { Story, Meta } from "@storybook/web-components";
import { html } from "lit-html";
/**
 * CopyList 컴포넌트 등록
 */
import "@/components/CopyList";

export default {
  title: "CopyList",
} as Meta;

type Args = {
  parameters: { design: Record<string, string>; store: Record<string, object> };
};

const Template: Story<Args> = () =>
  html`<copy-list></copy-list>`;

/**
 * parameters에 data를 작성한다.
 */
export const Default = Template.bind({});
Default.parameters = {
  data: {
    copyList:["a","b","c"],
  },
};

mock 작성하기

먼저 root에 __mocks__ 파일을 만들어줍니다.

// __mocks__/classes/Channel.ts
let data = [];
export default class Channel {
  copyList = [];
  constructor() {
    this.copyList = data;
  }
}

export function decorator(story, { parameters }) {
  if (parameters && parameters.data) {
    data = parameters.data;
  }
  return story();
}

Channel.copyList가 반환할 decorator를 만들어줍니다. 이 데코레이터는 parameters.data를 받아 Channel.copyList 가 반환할 값을 설정해줍니다. 즉

export const Default = Template.bind({});
Default.parameters = {
  data: {
    copyList:["a","b","c"],
  },
};

스토리에서 parameter로 설정한 값을 받아 저장하고 Channel에 접근시 이 데이터를 반환하게 할겁니다.

alias 수정하기

.storybook/main.cjs 파일에서 webpackFinal 혹은 viteFinal 속성을 수정합니다.

// .storybook/main.cjs
const { mergeConfig } = require("vite");
const { resolve } = require("path");

module.exports = {
	... // 불필요한 코드 생략
  async viteFinal(config) {
    return mergeConfig(config, {
      resolve: {
        alias: {
          "@/classes/Channel": require.resolve(
            "../__mocks__/classes/Channel.ts"
          ),
          "@mocks": resolve(__dirname, "../__mocks__"),
          "@": resolve(__dirname, "../src"),
        },
      },
    });
  },
};

이렇게 되면 @/classes/Channel 경로를 import 하는 코드가 스토리북에선 ../__mocks__/classes/Channel.ts 를 import하도록 동작하게 됩니다.

decorator 등록하기

// .storybook/preview.cjs

import { html } from "lit-html";
import { decorator as channelDecorator } from "/__mocks__/classes/Channel.ts";

export const decorators = [
  channelDecorator,
];

결과

CopyList.stories.ts 파일에 parameters.data를 적용하면

스토리북에서 이렇게 볼 수 있습니다.

profile
공부,번역하고 정리하는 곳

0개의 댓글