storybook/code/lib/core-common/src/presets.test.ts
Norbert de Langen c2bbe43d02
stage0
2022-07-21 11:24:07 +02:00

596 lines
16 KiB
TypeScript

function wrapPreset(basePresets: any): { babel: Function; webpack: Function } {
return {
babel: async (config: any, args: any) => basePresets.apply('babel', config, args),
webpack: async (config: any, args: any) => basePresets.apply('webpack', config, args),
};
}
function mockPreset(name: string, mockPresetObject: any) {
jest.mock(name, () => mockPresetObject, { virtual: true });
}
jest.mock('@storybook/node-logger', () => ({
logger: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('./utils/safeResolve', () => {
const KNOWN_FILES = [
'@storybook/react',
'@storybook/addon-actions/manager',
'@storybook/addon-actions/register',
'./local/preset',
'./local/addons',
'/absolute/preset',
'/absolute/addons',
'@storybook/addon-docs',
'@storybook/addon-cool',
'@storybook/addon-docs/preset',
'@storybook/addon-interactions/preset',
'@storybook/addon-essentials',
'@storybook/addon-knobs/manager',
'@storybook/addon-knobs/register',
'@storybook/addon-notes/register-panel',
'@storybook/preset-create-react-app',
'@storybook/preset-typescript',
'addon-bar/preset.js',
'addon-bar',
'addon-baz/register.js',
'addon-foo/register.js',
];
return {
safeResolveFrom: jest.fn((l: any, name: string) => {
if (KNOWN_FILES.includes(name)) {
return name;
}
return undefined;
}),
safeResolve: jest.fn((name: string) => {
if (KNOWN_FILES.includes(name)) {
return name;
}
return undefined;
}),
};
});
describe('presets', () => {
it('does not throw when there is no preset file', async () => {
const { getPresets } = jest.requireActual('./presets');
let presets;
async function testPresets() {
presets = wrapPreset(await getPresets());
await presets.webpack();
await presets.babel();
}
await expect(testPresets()).resolves.toBeUndefined();
expect(presets).toBeDefined();
});
it('does not throw when presets are empty', async () => {
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(await getPresets([]));
async function testPresets() {
await presets.webpack();
await presets.babel();
}
await expect(testPresets()).resolves.toBeUndefined();
});
it('does not throw when preset can not be loaded', async () => {
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(await getPresets(['preset-foo']));
async function testPresets() {
await presets.webpack();
await presets.babel();
}
await expect(testPresets()).resolves.toBeUndefined();
});
it('loads and applies presets when they are combined in another preset', async () => {
mockPreset('preset-foo', {
foo: (exec: string[]) => exec.concat('foo'),
});
mockPreset('preset-bar', {
foo: (exec: string[]) => exec.concat('bar'),
});
mockPreset('preset-got', [
'preset-dracarys',
{ name: 'preset-valar', options: { custom: 'morghulis' } },
]);
mockPreset('preset-dracarys', {
foo: (exec: string[]) => exec.concat('dracarys'),
});
mockPreset('preset-valar', {
foo: (exec: string[], options: any) => exec.concat(`valar ${options.custom}`),
});
const { getPresets } = jest.requireActual('./presets');
const presets = await getPresets(['preset-foo', 'preset-got', 'preset-bar'], {});
const result = await presets.apply('foo', []);
expect(result).toEqual(['foo', 'dracarys', 'valar morghulis', 'bar']);
});
it('loads and applies presets when they are declared as a string', async () => {
const mockPresetFooExtendWebpack = jest.fn();
const mockPresetBarExtendBabel = jest.fn();
mockPreset('preset-foo', {
webpack: mockPresetFooExtendWebpack,
});
mockPreset('preset-bar', {
babel: mockPresetBarExtendBabel,
});
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(await getPresets(['preset-foo', 'preset-bar'], {}));
async function testPresets() {
await presets.webpack();
await presets.babel();
}
await expect(testPresets()).resolves.toBeUndefined();
expect(mockPresetFooExtendWebpack).toHaveBeenCalled();
expect(mockPresetBarExtendBabel).toHaveBeenCalled();
});
it('loads and applies presets when they are declared as an object without props', async () => {
const mockPresetFooExtendWebpack = jest.fn();
const mockPresetBarExtendBabel = jest.fn();
mockPreset('preset-foo', {
webpack: mockPresetFooExtendWebpack,
});
mockPreset('preset-bar', {
babel: mockPresetBarExtendBabel,
});
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(
await getPresets([{ name: 'preset-foo' }, { name: 'preset-bar' }], {})
);
async function testPresets() {
await presets.webpack();
await presets.babel();
}
await expect(testPresets()).resolves.toBeUndefined();
expect(mockPresetFooExtendWebpack).toHaveBeenCalled();
expect(mockPresetBarExtendBabel).toHaveBeenCalled();
});
it('loads and applies presets when they are declared as an object with props', async () => {
const mockPresetFooExtendWebpack = jest.fn();
const mockPresetBarExtendBabel = jest.fn();
mockPreset('preset-foo', {
webpack: mockPresetFooExtendWebpack,
});
mockPreset('preset-bar', {
babel: mockPresetBarExtendBabel,
});
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(
await getPresets(
[
{ name: 'preset-foo', options: { foo: 1 } },
{ name: 'preset-bar', options: { bar: 'a' } },
],
{}
)
);
async function testPresets() {
await presets.webpack({});
await presets.babel({});
}
await expect(testPresets()).resolves.toBeUndefined();
expect(mockPresetFooExtendWebpack).toHaveBeenCalledWith(expect.anything(), {
foo: 1,
presetsList: expect.anything(),
presets: expect.anything(),
});
expect(mockPresetBarExtendBabel).toHaveBeenCalledWith(expect.anything(), {
bar: 'a',
presetsList: expect.anything(),
presets: expect.anything(),
});
});
it('loads and applies presets when they are declared as a string and as an object', async () => {
const mockPresetFooExtendWebpack = jest.fn();
const mockPresetBarExtendBabel = jest.fn();
mockPreset('preset-foo', {
webpack: mockPresetFooExtendWebpack,
});
mockPreset('preset-bar', {
babel: mockPresetBarExtendBabel,
});
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(
await getPresets(
[
'preset-foo',
{
name: 'preset-bar',
options: {
bar: 'a',
},
},
],
{}
)
);
async function testPresets() {
await presets.webpack({});
await presets.babel({});
}
await expect(testPresets()).resolves.toBeUndefined();
expect(mockPresetFooExtendWebpack).toHaveBeenCalled();
expect(mockPresetBarExtendBabel).toHaveBeenCalledWith(expect.anything(), {
bar: 'a',
presetsList: expect.arrayContaining([
expect.objectContaining({ name: 'preset-foo' }),
expect.objectContaining({ name: 'preset-bar' }),
]),
presets: expect.anything(),
});
});
it('applies presets in chain', async () => {
const mockPresetFooExtendWebpack = jest.fn((...args: any[]) => ({}));
const mockPresetBarExtendWebpack = jest.fn((...args: any[]) => ({}));
mockPreset('preset-foo', {
webpack: mockPresetFooExtendWebpack,
});
mockPreset('preset-bar', {
webpack: mockPresetBarExtendWebpack,
});
const { getPresets } = jest.requireActual('./presets');
const presets = wrapPreset(
await getPresets(
[
'preset-foo',
{
name: 'preset-bar',
options: {
bar: 'a',
presetsList: expect.arrayContaining([
expect.objectContaining({ name: 'preset-foo' }),
expect.objectContaining({ name: 'preset-bar' }),
]),
presets: expect.anything(),
},
},
],
{}
)
);
async function testPresets() {
await presets.webpack();
await presets.babel();
}
await expect(testPresets()).resolves.toBeUndefined();
expect(mockPresetFooExtendWebpack).toHaveBeenCalled();
expect(mockPresetBarExtendWebpack).toHaveBeenCalledWith(expect.anything(), {
bar: 'a',
presetsList: expect.arrayContaining([
expect.objectContaining({ name: 'preset-foo' }),
expect.objectContaining({ name: 'preset-bar' }),
]),
presets: expect.anything(),
});
});
it('allows for presets to export presets array', async () => {
const { getPresets } = jest.requireActual('./presets');
const input = {};
const mockPresetBar = jest.fn((...args: any[]) => input);
mockPreset('preset-foo', {
presets: ['preset-bar'],
});
mockPreset('preset-bar', {
bar: mockPresetBar,
});
const presets = await getPresets(['preset-foo'], {});
const output = await presets.apply('bar');
expect(mockPresetBar).toHaveBeenCalledWith(undefined, expect.any(Object));
expect(input).toBe(output);
});
it('allows for presets to export presets fn', async () => {
const { getPresets } = jest.requireActual('./presets');
const input = {};
const storybookOptions = { a: 1 };
const presetOptions = { b: 2 };
const mockPresetBar = jest.fn((...args: any[]) => input);
const mockPresetFoo = jest.fn((...args: any[]) => ['preset-bar']);
mockPreset('preset-foo', {
presets: mockPresetFoo,
});
mockPreset('preset-bar', {
bar: mockPresetBar,
});
const presets = await getPresets([{ name: 'preset-foo', options: { b: 2 } }], storybookOptions);
const output = await presets.apply('bar');
expect(mockPresetFoo).toHaveBeenCalledWith({ ...storybookOptions, ...presetOptions });
expect(mockPresetBar).toHaveBeenCalledWith(undefined, expect.any(Object));
expect(input).toBe(output);
});
afterEach(() => {
jest.resetModules();
});
});
describe('resolveAddonName', () => {
const { resolveAddonName } = jest.requireActual('./presets');
it('should resolve packages with metadata (relative path)', () => {
mockPreset('./local/preset', {
presets: [],
});
expect(resolveAddonName({}, './local/preset')).toEqual({
name: './local/preset',
type: 'presets',
});
});
it('should resolve packages with metadata (absolute path)', () => {
mockPreset('/absolute/preset', {
presets: [],
});
expect(resolveAddonName({}, '/absolute/preset')).toEqual({
name: '/absolute/preset',
type: 'presets',
});
});
it('should resolve packages without metadata', () => {
expect(resolveAddonName({}, '@storybook/preset-create-react-app')).toEqual({
name: '@storybook/preset-create-react-app',
type: 'presets',
});
});
it('should resolve managerEntries', () => {
expect(resolveAddonName({}, '@storybook/addon-actions/register')).toEqual({
name: '@storybook/addon-actions/register',
managerEntries: ['@storybook/addon-actions/register'],
type: 'virtual',
});
});
it('should resolve managerEntries from new /manager path', () => {
expect(resolveAddonName({}, '@storybook/addon-actions/manager')).toEqual({
name: '@storybook/addon-actions/manager',
managerEntries: ['@storybook/addon-actions/manager'],
type: 'virtual',
});
});
it('should resolve presets', () => {
expect(resolveAddonName({}, '@storybook/addon-docs/preset')).toEqual({
name: '@storybook/addon-docs/preset',
type: 'presets',
});
});
it('should resolve preset packages', () => {
expect(resolveAddonName({}, '@storybook/addon-essentials')).toEqual({
name: '@storybook/addon-essentials',
type: 'presets',
});
});
it('should error on invalid inputs', () => {
expect(() => resolveAddonName({}, null)).toThrow();
});
});
describe('loadPreset', () => {
mockPreset('@storybook/react', {});
mockPreset('@storybook/preset-typescript', {});
mockPreset('@storybook/addon-docs/preset', {});
mockPreset('@storybook/addon-actions/register', {});
mockPreset('addon-foo/register.js', {});
mockPreset('@storybook/addon-cool', {});
mockPreset('@storybook/addon-interactions/preset', {});
mockPreset('addon-bar', {
addons: ['@storybook/addon-cool'],
presets: ['@storybook/addon-interactions/preset'],
});
mockPreset('addon-baz/register.js', {});
mockPreset('@storybook/addon-notes/register-panel', {});
const { loadPreset } = jest.requireActual('./presets');
it('should prepend framework field to list of presets', async () => {
const loaded = await loadPreset(
{
name: '',
type: 'virtual',
framework: '@storybook/react',
presets: ['@storybook/preset-typescript'],
addons: ['@storybook/addon-docs/preset'],
},
0,
{}
);
expect(loaded).toMatchInlineSnapshot(`
Array [
Object {
"name": "@storybook/preset-typescript",
"options": Object {},
"preset": Object {},
},
Object {
"name": "@storybook/addon-docs/preset",
"options": Object {},
"preset": Object {},
},
Object {
"name": Object {
"addons": Array [
"@storybook/addon-docs/preset",
],
"framework": "@storybook/react",
"name": "",
"presets": Array [
"@storybook/preset-typescript",
],
"type": "virtual",
},
"options": Object {},
"preset": Object {
"framework": "@storybook/react",
},
},
]
`);
});
it('should resolve all addons & presets in correct order', async () => {
const loaded = await loadPreset(
{
name: '',
type: 'virtual',
presets: ['@storybook/preset-typescript'],
addons: [
'@storybook/addon-docs/preset',
'@storybook/addon-actions/register',
'addon-foo/register.js',
'addon-bar',
'addon-baz/register.js',
'@storybook/addon-notes/register-panel',
],
},
0,
{}
);
expect(loaded).toEqual([
{
name: '@storybook/preset-typescript',
options: {},
preset: {},
},
{
name: '@storybook/addon-docs/preset',
options: {},
preset: {},
},
{
name: '@storybook/addon-actions/register',
options: {},
preset: {
managerEntries: ['@storybook/addon-actions/register'],
},
},
{
name: 'addon-foo/register.js',
options: {},
preset: {
managerEntries: ['addon-foo/register.js'],
},
},
{
name: '@storybook/addon-interactions/preset',
options: {},
preset: {},
},
{
name: '@storybook/addon-cool',
options: {},
preset: {},
},
{
name: 'addon-bar',
options: {},
preset: {},
},
{
name: 'addon-baz/register.js',
options: {},
preset: {
managerEntries: ['addon-baz/register.js'],
},
},
{
name: '@storybook/addon-notes/register-panel',
options: {},
preset: {
managerEntries: ['@storybook/addon-notes/register-panel'],
},
},
{
name: {
presets: ['@storybook/preset-typescript'],
addons: [
'@storybook/addon-docs/preset',
'@storybook/addon-actions/register',
'addon-foo/register.js',
'addon-bar',
'addon-baz/register.js',
'@storybook/addon-notes/register-panel',
],
name: '',
type: 'virtual',
},
options: {},
preset: {},
},
]);
});
});