@storybook/addon-interactionsがどのようにspyを設定するか確認したときのメモ
背景
@storybook/addon-interactionsには@storybook/addon-actionsと連携して、アクションにjest.fn()
を挿入してくれる機能がある。これをcomposeStoryでも使いたいがどういう仕組になっているか確認する。
結論
雑に言うと、Storyのargsを読み取り、@storybook/addon-actionsによって追加されたプロパティがあった場合、そこにjest.fn()
を挿入する。これは@storybook/addon-interactionsで行われる。
背景で書いた、composeStoryで利用する場合は、これらの近いことをする必要がある。なぜなら、composeStoryではaddonには全く依存しておらず、利用していないためである。つまり、actionsやinteractionsでやっていることと同様のことをする必要がある。
なお、難しくはないが、少し面倒ではある。コンポーネントにわたすプロパティのargTypesの自動生成と、argTypesを読み直してargsにjest.fn()
を追加してやれば実現できるだろう。
もっと詳しく
if (typeof val === 'function' && val.name === 'actionHandler') {
Object.defineProperty(val, 'name', { value: key, writable: false });
Object.defineProperty(val, '__storyId__', { value: id, writable: false });
acc[key] = action(val);
spies.push(acc[key]);
return acc;
}
acc[key] = val;
action(val)
が実質jest.fn(val)
である。instrumentはinteractionに必要な追加処理を加える高階関数と捉えて問題ないだろう。
const { action } = instrument({ action: fn }, { retain: true });
@storybook/addon-actionsによって追加されたプロパティは関数で、actionHandler
という名前である。
これをどこで加えているかはaddArgsHelpers.ts
で定義されている関数をみてみると、@storybook/addon-actionsに利用するargsTypes
をループで回してnameを確認したり、parameters.actions.argTypesRegex
を利用している様子が伺える。
例えば、inferActionsFromArgTypesRegexではargTypesをentriesでループを回し、argTypesRegexにマッチするか確認し、action関数でactionHandlerを生成し、argsに加えている。
const argTypesRegex = new RegExp(actions.argTypesRegex);
const argTypesMatchingRegex = Object.entries(argTypes).filter(
([name]) => !!argTypesRegex.test(name)
);
return argTypesMatchingRegex.reduce((acc, [name, argType]) => {
if (typeof initialArgs[name] === 'undefined') {
acc[name] = action(name);
}
return acc;
}, {} as Args);
argTypesは別途react-docgen-typescript
のようなライブラリなので追加されていると考えられる。
Discussion