mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 11:11:53 +08:00
Merge pull request #28905 from storybookjs/yann/improve-vitest-viewport
Addon Vitest: Set default viewport if applicable
This commit is contained in:
commit
68afda85b9
151
code/addons/vitest/src/plugin/viewports.test.ts
Normal file
151
code/addons/vitest/src/plugin/viewports.test.ts
Normal file
@ -0,0 +1,151 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { page } from '@vitest/browser/context';
|
||||
|
||||
import { DEFAULT_VIEWPORT_DIMENSIONS, type ViewportsParam, setViewport } from './viewports';
|
||||
|
||||
vi.mock('@vitest/browser/context', () => ({
|
||||
page: {
|
||||
viewport: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('setViewport', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
globalThis.__vitest_browser__ = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.__vitest_browser__ = false;
|
||||
});
|
||||
|
||||
it('should no op outside when not in Vitest browser mode', async () => {
|
||||
globalThis.__vitest_browser__ = false;
|
||||
|
||||
await setViewport();
|
||||
expect(page.viewport).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fall back to DEFAULT_VIEWPORT_DIMENSIONS if defaultViewport does not exist', async () => {
|
||||
const viewportsParam: any = {
|
||||
defaultViewport: 'nonExistentViewport',
|
||||
};
|
||||
|
||||
await setViewport(viewportsParam);
|
||||
expect(page.viewport).toHaveBeenCalledWith(1200, 900);
|
||||
});
|
||||
|
||||
it('should set the dimensions of viewport from INITIAL_VIEWPORTS', async () => {
|
||||
const viewportsParam: any = {
|
||||
// supported by default in addon viewports
|
||||
defaultViewport: 'ipad',
|
||||
};
|
||||
|
||||
await setViewport(viewportsParam);
|
||||
expect(page.viewport).toHaveBeenCalledWith(768, 1024);
|
||||
});
|
||||
|
||||
it('should set custom defined viewport dimensions', async () => {
|
||||
const viewportsParam: ViewportsParam = {
|
||||
defaultViewport: 'customViewport',
|
||||
viewports: {
|
||||
customViewport: {
|
||||
name: 'Custom Viewport',
|
||||
type: 'mobile',
|
||||
styles: {
|
||||
width: '800px',
|
||||
height: '600px',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await setViewport(viewportsParam);
|
||||
expect(page.viewport).toHaveBeenCalledWith(800, 600);
|
||||
});
|
||||
|
||||
it('should correctly handle percentage-based dimensions', async () => {
|
||||
const viewportsParam: ViewportsParam = {
|
||||
defaultViewport: 'percentageViewport',
|
||||
viewports: {
|
||||
percentageViewport: {
|
||||
name: 'Percentage Viewport',
|
||||
type: 'desktop',
|
||||
styles: {
|
||||
width: '50%',
|
||||
height: '50%',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await setViewport(viewportsParam);
|
||||
expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080
|
||||
});
|
||||
|
||||
it('should correctly handle vw and vh based dimensions', async () => {
|
||||
const viewportsParam: ViewportsParam = {
|
||||
defaultViewport: 'viewportUnits',
|
||||
viewports: {
|
||||
viewportUnits: {
|
||||
name: 'VW/VH Viewport',
|
||||
type: 'desktop',
|
||||
styles: {
|
||||
width: '50vw',
|
||||
height: '50vh',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await setViewport(viewportsParam);
|
||||
expect(page.viewport).toHaveBeenCalledWith(600, 450); // 50% of 1920 and 1080
|
||||
});
|
||||
|
||||
it('should correctly handle em based dimensions', async () => {
|
||||
const viewportsParam: ViewportsParam = {
|
||||
defaultViewport: 'viewportUnits',
|
||||
viewports: {
|
||||
viewportUnits: {
|
||||
name: 'em/rem Viewport',
|
||||
type: 'mobile',
|
||||
styles: {
|
||||
width: '20em',
|
||||
height: '40rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await setViewport(viewportsParam);
|
||||
expect(page.viewport).toHaveBeenCalledWith(320, 640); // dimensions * 16
|
||||
});
|
||||
|
||||
it('should throw an error for unsupported dimension values', async () => {
|
||||
const viewportsParam: ViewportsParam = {
|
||||
defaultViewport: 'invalidViewport',
|
||||
viewports: {
|
||||
invalidViewport: {
|
||||
name: 'Invalid Viewport',
|
||||
type: 'desktop',
|
||||
styles: {
|
||||
width: 'calc(100vw - 20px)',
|
||||
height: '10pc',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await expect(setViewport(viewportsParam)).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
[SB_ADDON_VITEST_0001 (UnsupportedViewportDimensionError): Encountered an unsupported value "calc(100vw - 20px)" when setting the viewport width dimension.
|
||||
|
||||
The Storybook plugin only supports values in the following units:
|
||||
- px, vh, vw, em, rem and %.
|
||||
|
||||
You can either change the viewport for this story to use one of the supported units or skip the test by adding '!test' to the story's tags per https://storybook.js.org/docs/writing-stories/tags]
|
||||
`);
|
||||
expect(page.viewport).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -1,4 +1,6 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { UnsupportedViewportDimensionError } from 'storybook/internal/preview-errors';
|
||||
|
||||
import { page } from '@vitest/browser/context';
|
||||
|
||||
import { INITIAL_VIEWPORTS } from '../../../viewport/src/defaults';
|
||||
@ -9,16 +11,47 @@ declare global {
|
||||
var __vitest_browser__: boolean;
|
||||
}
|
||||
|
||||
interface ViewportsParam {
|
||||
export interface ViewportsParam {
|
||||
defaultViewport: string;
|
||||
viewports: ViewportMap;
|
||||
}
|
||||
|
||||
export const DEFAULT_VIEWPORT_DIMENSIONS = {
|
||||
width: 1200,
|
||||
height: 900,
|
||||
};
|
||||
|
||||
const validPixelOrNumber = /^\d+(px)?$/;
|
||||
const percentagePattern = /^(\d+(\.\d+)?%)$/;
|
||||
const vwPattern = /^(\d+(\.\d+)?vw)$/;
|
||||
const vhPattern = /^(\d+(\.\d+)?vh)$/;
|
||||
const emRemPattern = /^(\d+)(em|rem)$/;
|
||||
|
||||
const parseDimension = (value: string, dimension: 'width' | 'height') => {
|
||||
if (validPixelOrNumber.test(value)) {
|
||||
return Number.parseInt(value, 10);
|
||||
} else if (percentagePattern.test(value)) {
|
||||
const percentageValue = parseFloat(value) / 100;
|
||||
return Math.round(DEFAULT_VIEWPORT_DIMENSIONS[dimension] * percentageValue);
|
||||
} else if (vwPattern.test(value)) {
|
||||
const vwValue = parseFloat(value) / 100;
|
||||
return Math.round(DEFAULT_VIEWPORT_DIMENSIONS.width * vwValue);
|
||||
} else if (vhPattern.test(value)) {
|
||||
const vhValue = parseFloat(value) / 100;
|
||||
return Math.round(DEFAULT_VIEWPORT_DIMENSIONS.height * vhValue);
|
||||
} else if (emRemPattern.test(value)) {
|
||||
const emRemValue = Number.parseInt(value, 10);
|
||||
return emRemValue * 16;
|
||||
} else {
|
||||
throw new UnsupportedViewportDimensionError({ dimension, value });
|
||||
}
|
||||
};
|
||||
|
||||
export const setViewport = async (viewportsParam: ViewportsParam = {} as ViewportsParam) => {
|
||||
const defaultViewport = viewportsParam.defaultViewport;
|
||||
|
||||
if (!page || !globalThis.__vitest_browser__ || !defaultViewport) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const viewports = {
|
||||
@ -26,16 +59,17 @@ export const setViewport = async (viewportsParam: ViewportsParam = {} as Viewpor
|
||||
...viewportsParam.viewports,
|
||||
};
|
||||
|
||||
let viewportWidth = DEFAULT_VIEWPORT_DIMENSIONS.width;
|
||||
let viewportHeight = DEFAULT_VIEWPORT_DIMENSIONS.height;
|
||||
|
||||
if (defaultViewport in viewports) {
|
||||
const styles = viewports[defaultViewport].styles as ViewportStyles;
|
||||
if (styles?.width && styles?.height) {
|
||||
const { width, height } = {
|
||||
width: Number.parseInt(styles.width, 10),
|
||||
height: Number.parseInt(styles.height, 10),
|
||||
};
|
||||
await page.viewport(width, height);
|
||||
const { width, height } = styles;
|
||||
viewportWidth = parseDimension(width, 'width');
|
||||
viewportHeight = parseDimension(height, 'height');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
await page.viewport(viewportWidth, viewportHeight);
|
||||
};
|
||||
|
@ -30,6 +30,7 @@ export enum Category {
|
||||
RENDERER_VUE3 = 'RENDERER_VUE3',
|
||||
RENDERER_WEB_COMPONENTS = 'RENDERER_WEB-COMPONENTS',
|
||||
FRAMEWORK_NEXTJS = 'FRAMEWORK_NEXTJS',
|
||||
ADDON_VITEST = 'ADDON_VITEST',
|
||||
}
|
||||
|
||||
export class MissingStoryAfterHmrError extends StorybookError {
|
||||
@ -317,3 +318,22 @@ export class UnknownArgTypesError extends StorybookError {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsupportedViewportDimensionError extends StorybookError {
|
||||
constructor(public data: { dimension: string; value: string }) {
|
||||
super({
|
||||
category: Category.ADDON_VITEST,
|
||||
code: 1,
|
||||
// TODO: Add documentation about viewports support
|
||||
// documentation: '',
|
||||
message: dedent`
|
||||
Encountered an unsupported value "${data.value}" when setting the viewport ${data.dimension} dimension.
|
||||
|
||||
The Storybook plugin only supports values in the following units:
|
||||
- px, vh, vw, em, rem and %.
|
||||
|
||||
You can either change the viewport for this story to use one of the supported units or skip the test by adding '!test' to the story's tags per https://storybook.js.org/docs/writing-stories/tags
|
||||
`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user