diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 13aba8e45642..825656ad4a91 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,8 @@ +## 10.2.0-alpha.13 + +- Core: Fix onboarding visual bugs, survey telemetry and modal dismissal - [#33326](https://github.com/storybookjs/storybook/pull/33326), thanks @ghengeveld! +- Core: Track vision simulator state through globals and apply styles in preview - [#33418](https://github.com/storybookjs/storybook/pull/33418), thanks @ghengeveld! + ## 10.2.0-alpha.12 - Addon-docs: Add MDX manifest generation - [#33408](https://github.com/storybookjs/storybook/pull/33408), thanks @copilot-swe-agent! diff --git a/code/addons/a11y/src/components/A11YPanel.test.tsx b/code/addons/a11y/src/components/A11YPanel.test.tsx index b39be8e1712a..9352981fc613 100644 --- a/code/addons/a11y/src/components/A11YPanel.test.tsx +++ b/code/addons/a11y/src/components/A11YPanel.test.tsx @@ -1,4 +1,5 @@ // @vitest-environment happy-dom +/// import { fireEvent, render } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; diff --git a/code/addons/a11y/src/components/ColorFilters.tsx b/code/addons/a11y/src/components/ColorFilters.tsx deleted file mode 100644 index 24c82e9f95cb..000000000000 --- a/code/addons/a11y/src/components/ColorFilters.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import * as React from 'react'; - -export const Filters: React.FC> = (props) => ( - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/code/addons/a11y/src/components/VisionSimulator.stories.tsx b/code/addons/a11y/src/components/VisionSimulator.stories.tsx new file mode 100644 index 000000000000..ccbf09fbe583 --- /dev/null +++ b/code/addons/a11y/src/components/VisionSimulator.stories.tsx @@ -0,0 +1,57 @@ +import type { PlayFunction, PlayFunctionContext } from 'storybook/internal/types'; + +import { ManagerContext } from 'storybook/manager-api'; +import { expect, fn, screen } from 'storybook/test'; + +import preview from '../../../../.storybook/preview'; +import { VisionSimulator } from './VisionSimulator'; + +const managerContext: any = { + state: {}, + api: { + getGlobals: fn(() => ({ vision: undefined })), + updateGlobals: fn(), + getStoryGlobals: fn(() => ({ vision: undefined })), + getUserGlobals: fn(() => ({ vision: undefined })), + }, +}; + +const meta = preview.meta({ + title: 'Vision Simulator', + component: VisionSimulator, + decorators: [ + (Story: any) => ( + + + + ), + ], +}); + +export default meta; + +const openMenu: PlayFunction = async ({ canvas, userEvent }) => { + await userEvent.click(canvas.getByRole('button', { name: 'Vision simulator' })); +}; + +export const Default = meta.story({ + play: openMenu, +}); + +export const WithFilter = meta.story({ + play: openMenu, + globals: { + vision: 'achromatopsia', + }, +}); + +export const Selection = meta.story({ + play: async (context) => { + await openMenu(context); + await context.userEvent.click(await screen.findByText('Blurred vision')); + await expect(managerContext.api.updateGlobals).toHaveBeenCalledWith({ vision: 'blurred' }); + await expect( + context.canvas.getByRole('button', { name: 'Vision simulator Blurred vision' }) + ).toBeVisible(); + }, +}); diff --git a/code/addons/a11y/src/components/VisionSimulator.test.tsx b/code/addons/a11y/src/components/VisionSimulator.test.tsx deleted file mode 100644 index 7b0ef88c9290..000000000000 --- a/code/addons/a11y/src/components/VisionSimulator.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -// @vitest-environment happy-dom -/// ; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { describe, expect, it } from 'vitest'; - -import React from 'react'; - -import { ThemeProvider, convert, themes } from 'storybook/theming'; - -import { VisionSimulator, baseList } from './VisionSimulator'; - -const getOptionByNameAndPercentage = (option: string, percentage?: number) => - screen.getByText( - (content, element) => - content !== '' && - // @ts-expect-error (TODO) - element.textContent === option && - // @ts-expect-error (TODO) - (percentage === undefined || element.nextSibling.textContent === `${percentage}% of users`) - ); - -function ThemedVisionSimulator() { - return ( - - - - ); -} - -describe('Vision Simulator', () => { - // TODO: there are issues with the ThemeProvider from lib/theming for some reason - // which are causing rendering issues in the component for all these tests - it.skip('should render tool button', async () => { - // when - render(); - - // then - // waitFor because WithTooltip is a lazy component - await waitFor(() => expect(screen.getByTitle('Vision simulator')).toBeInTheDocument()); - }); - - it.skip('should display tooltip on click', async () => { - // given - render(); - await waitFor(() => expect(screen.getByTitle('Vision simulator')).toBeInTheDocument()); - - // when - userEvent.click(screen.getByRole('button', { name: 'Vision simulator' })); - - // then - await waitFor(() => expect(screen.getByText('blurred vision')).toBeInTheDocument()); - baseList.forEach(({ name, percentage }) => - expect(getOptionByNameAndPercentage(name, percentage)).toBeInTheDocument() - ); - }); - - it.skip('should set filter', async () => { - // given - render(); - await waitFor(() => expect(screen.getByTitle('Vision simulator')).toBeInTheDocument()); - userEvent.click(screen.getByRole('button', { name: 'Vision simulator' })); - await waitFor(() => expect(screen.getByText('blurred vision')).toBeInTheDocument()); - - // when - fireEvent.click(screen.getByText('blurred vision')); - - // then - const rule = Object.values(document.styleSheets) - .filter(({ cssRules }) => cssRules) - .map(({ cssRules }) => Object.values(cssRules)) - .flat() - // @ts-expect-error (TODO) - .find((cssRule: CSSRule) => cssRule.selectorText === '#storybook-preview-iframe'); - - expect(rule).toBeDefined(); - // @ts-expect-error (TODO) - expect(rule.style.filter).toBe('blur(2px)'); - }); -}); diff --git a/code/addons/a11y/src/components/VisionSimulator.tsx b/code/addons/a11y/src/components/VisionSimulator.tsx index c029abe20332..4783a556ed57 100644 --- a/code/addons/a11y/src/components/VisionSimulator.tsx +++ b/code/addons/a11y/src/components/VisionSimulator.tsx @@ -1,46 +1,14 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Select } from 'storybook/internal/components'; import { AccessibilityIcon } from '@storybook/icons'; -import { Global, styled } from 'storybook/theming'; +import { useGlobals } from 'storybook/manager-api'; +import { styled } from 'storybook/theming'; -import { Filters } from './ColorFilters'; - -const iframeId = 'storybook-preview-iframe'; - -interface Option { - name: string; - percentage?: number; -} - -export const baseList = [ - { name: 'blurred vision', percentage: 22.9 }, - { name: 'deuteranomaly', percentage: 2.7 }, - { name: 'deuteranopia', percentage: 0.56 }, - { name: 'protanomaly', percentage: 0.66 }, - { name: 'protanopia', percentage: 0.59 }, - { name: 'tritanomaly', percentage: 0.01 }, - { name: 'tritanopia', percentage: 0.016 }, - { name: 'achromatopsia', percentage: 0.0001 }, - { name: 'grayscale' }, -] as Option[]; - -type Filter = Option | null; - -const getFilter = (filterName: string) => { - if (!filterName) { - return 'none'; - } - if (filterName === 'blurred vision') { - return 'blur(2px)'; - } - if (filterName === 'grayscale') { - return 'grayscale(100%)'; - } - return `url('#${filterName}')`; -}; +import { VISION_GLOBAL_KEY } from '../constants'; +import { filterDefs, filters } from '../visionSimulatorFilters'; const Hidden = styled.div({ '&, & svg': { @@ -59,7 +27,7 @@ const ColorIcon = styled.span<{ $filter: string }>( width: '1rem', }, ({ $filter }) => ({ - filter: getFilter($filter), + filter: filters[$filter as keyof typeof filters].filter || 'none', }), ({ theme }) => ({ boxShadow: `${theme.appBorderColor} 0 0 0 1px inset`, @@ -67,41 +35,28 @@ const ColorIcon = styled.span<{ $filter: string }>( ); export const VisionSimulator = () => { - const [filter, setFilter] = useState(null); + const [globals, updateGlobals] = useGlobals(); + const value = globals[VISION_GLOBAL_KEY]; - const options = baseList.map(({ name, percentage }) => { - const description = percentage !== undefined ? `${percentage}% of users` : undefined; - return { - title: name, - description, - icon: , - value: name, - }; - }); + const options = Object.entries(filters).map(([key, { label, percentage }]) => ({ + title: label, + description: percentage ? `${percentage}% of users` : undefined, + icon: , + value: key, + })); return ( <> - {filter && ( - - )}