Tweak RNW CI

This commit is contained in:
Michael Shilman 2024-11-17 16:15:49 +08:00
parent 4fb8b02c69
commit ee7b69fead
21 changed files with 344 additions and 250 deletions

View File

@ -980,22 +980,22 @@ workflows:
requires:
- build
- create-sandboxes:
parallelism: 37
parallelism: 38
requires:
- build
# - smoke-test-sandboxes: # disabled for now
# requires:
# - create-sandboxes
- build-sandboxes:
parallelism: 37
parallelism: 38
requires:
- create-sandboxes
- chromatic-sandboxes:
parallelism: 34
parallelism: 35
requires:
- build-sandboxes
- e2e-production:
parallelism: 32
parallelism: 33
requires:
- build-sandboxes
- e2e-dev:
@ -1003,7 +1003,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 32
parallelism: 33
requires:
- build-sandboxes
- vitest-integration:

View File

@ -12,6 +12,10 @@ test.describe('addon-actions', () => {
templateName.includes('svelte') && templateName.includes('prerelease'),
'Svelte 5 prerelase does not support automatic actions with our current example components yet'
);
test.skip(
templateName.includes('react-native-web'),
'React Native uses onPress rather than onClick'
);
await page.goto(storybookUrl);
const sbPage = new SbPage(page, expect);
sbPage.waitUntilLoaded();

View File

@ -4,9 +4,12 @@ import process from 'process';
import { SbPage } from './util';
const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';
const templateName = process.env.STORYBOOK_TEMPLATE_NAME || '';
test.describe('addon-controls', () => {
test('should change component when changing controls', async ({ page }) => {
test.skip(templateName.includes('react-native-web'), 'React Native CSS behaves differently');
await page.goto(storybookUrl);
const sbPage = new SbPage(page, expect);
await sbPage.waitUntilLoaded();

View File

@ -123,6 +123,7 @@ test.describe('addon-docs', () => {
// - template: https://638db567ed97c3fb3e21cc22-ulhjwkqzzj.chromatic.com/?path=/docs/addons-docs-docspage-basic--docs
// - real: https://638db567ed97c3fb3e21cc22-ulhjwkqzzj.chromatic.com/?path=/docs/example-button--docs
'lit-vite',
'react-native-web',
];
test.skip(
new RegExp(`^${skipped.join('|')}`, 'i').test(`${templateName}`),

View File

@ -23,6 +23,10 @@ test.describe('addon-interactions', () => {
/^(lit)/i.test(`${templateName}`),
`Skipping ${templateName}, which does not support addon-interactions`
);
test.skip(
templateName.includes('react-native-web'),
'React Native does not use className locators'
);
const sbPage = new SbPage(page, expect);

View File

@ -1,8 +1,11 @@
import { fn } from '@storybook/test';
import { View } from 'react-native';
import { Button } from './Button';
const meta = {
title: 'Example/Button',
component: Button,
decorators: [
(Story) => (
@ -13,6 +16,8 @@ const meta = {
],
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// Use `fn` to spy on the onPress arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onPress: fn() },
};
export default meta;

View File

@ -12,11 +12,16 @@ export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (
<View style={styles.buttonContainer}>
{user ? (
<Button style={styles.button} size="small" onPress={onLogout} label="Log out" />
<>
<>
<Text>Welcome, </Text>
<Text style={styles.userName}>{user.name}!</Text>
</>
<Button style={styles.button} size="small" onPress={onLogout} label="Log out" />
</>
) : (
<>
<Button style={styles.button} size="small" onPress={onLogin} label="Log in" />
<Button
style={styles.button}
primary
@ -58,6 +63,10 @@ const styles = StyleSheet.create({
},
buttonContainer: {
flexDirection: 'row',
alignItems: 'center',
},
userName: {
fontWeight: '700',
},
});

View File

@ -1,6 +1,7 @@
import { Header } from './Header';
const meta = {
title: 'Example/Header',
component: Header,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
@ -10,7 +11,9 @@ export default meta;
export const LoggedIn = {
args: {
user: {},
user: {
name: 'Jane Doe',
},
onLogin: () => {},
onLogout: () => {},
onCreateAccount: () => {},

View File

@ -1,84 +1,95 @@
import { useState } from 'react';
import { Linking, StyleSheet, Text, View } from 'react-native';
import { Header } from './Header';
export const Page = ({ user, onLogin, onLogout, onCreateAccount }) => (
<View>
<Header user={user} onLogin={onLogin} onLogout={onLogout} onCreateAccount={onCreateAccount} />
export const Page = () => {
const [user, setUser] = useState();
<View style={styles.section}>
<Text role="heading" style={styles.h2}>
Pages in Storybook
</Text>
return (
<View>
<Header
user={user}
onLogin={() => setUser({ name: 'Jane Doe' })}
onLogout={() => setUser(undefined)}
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
/>
<Text style={styles.p}>
We recommend building UIs with a{' '}
<Text
style={[styles.a, { fontWeight: 'bold' }]}
role="link"
onPress={() => {
Linking.openURL('https://componentdriven.org');
}}
>
<Text>component-driven</Text>
</Text>{' '}
process starting with atomic components and ending with pages.
</Text>
<View style={styles.section}>
<Text role="heading" style={styles.h2}>
Pages in Storybook
</Text>
<Text style={styles.p}>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page data
in Storybook:
</Text>
<Text style={styles.p}>
We recommend building UIs with a{' '}
<Text
style={[styles.a, { fontWeight: 'bold' }]}
role="link"
onPress={() => {
Linking.openURL('https://componentdriven.org');
}}
>
<Text>component-driven</Text>
</Text>{' '}
process starting with atomic components and ending with pages.
</Text>
<View>
<View>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</View>
<Text style={styles.p}>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page
data in Storybook:
</Text>
<View>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</View>
</View>
<View>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</View>
<Text style={styles.p}>
Get a guided tutorial on component-driven development at{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/tutorials/');
}}
>
Storybook tutorials
</Text>
. Read more in the{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/docs');
}}
>
docs
</Text>
.
</Text>
<View style={styles.tipWrapper}>
<View style={styles.tip}>
<Text style={styles.tipText}>Tip </Text>
<View>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</View>
</View>
<Text>Adjust the width of the canvas with the </Text>
<Text style={styles.p}>
Get a guided tutorial on component-driven development at{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/tutorials/');
}}
>
Storybook tutorials
</Text>
. Read more in the{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/docs');
}}
>
docs
</Text>
.
</Text>
<Text>Viewports addon in the toolbar</Text>
<View style={styles.tipWrapper}>
<View style={styles.tip}>
<Text style={styles.tipText}>Tip </Text>
</View>
<Text>Adjust the width of the canvas with the </Text>
<Text>Viewports addon in the toolbar</Text>
</View>
</View>
</View>
</View>
);
);
};
const styles = StyleSheet.create({
section: {
@ -149,16 +160,3 @@ const styles = StyleSheet.create({
marginTop: 3,
},
});
Page.propTypes = {
user: PropTypes.shape({
name: PropTypes.string.isRequired,
}),
onLogin: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired,
onCreateAccount: PropTypes.func.isRequired,
};
Page.defaultProps = {
user: null,
};

View File

@ -1,16 +1,25 @@
import * as HeaderStories from './Header.stories';
import { expect, userEvent, within } from '@storybook/test';
import { Page } from './Page';
const meta = {
title: 'Example/Page',
component: Page,
};
export default meta;
export const LoggedIn = {
args: HeaderStories.LoggedIn.args,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const loginButton = canvas.getByRole('button', { name: /Log in/i });
await expect(loginButton).toBeInTheDocument();
await userEvent.click(loginButton);
// FIXME: await expect(loginButton).not.toBeInTheDocument();
const logoutButton = canvas.getByRole('button', { name: /Log out/i });
await expect(logoutButton).toBeInTheDocument();
},
};
export const LoggedOut = {
args: HeaderStories.LoggedOut.args,
};
export const LoggedOut = {};

View File

@ -1,10 +1,12 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { View } from 'react-native';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Example/Button',
component: Button,
decorators: [
(Story) => (
@ -15,6 +17,8 @@ const meta: Meta<typeof Button> = {
],
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// Use `fn` to spy on the onPress arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onPress: fn() },
};
export default meta;

View File

@ -3,6 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Header } from './Header';
const meta: Meta<typeof Header> = {
title: 'Example/Header',
component: Header,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
@ -14,7 +15,9 @@ type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = {
args: {
user: {},
user: {
name: 'Jane Doe',
},
onLogin: () => {},
onLogout: () => {},
onCreateAccount: () => {},

View File

@ -18,7 +18,13 @@ export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps
<View style={styles.buttonContainer}>
{user ? (
<Button style={styles.button} size="small" onPress={onLogout} label="Log out" />
<>
<>
<Text>Welcome, </Text>
<Text style={styles.userName}>{user.name}!</Text>
</>
<Button style={styles.button} size="small" onPress={onLogout} label="Log out" />
</>
) : (
<>
<Button style={styles.button} size="small" onPress={onLogin} label="Log in" />
@ -64,5 +70,9 @@ const styles = StyleSheet.create({
},
buttonContainer: {
flexDirection: 'row',
alignItems: 'center',
},
userName: {
fontWeight: '700',
},
});

View File

@ -1,9 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import * as HeaderStories from './Header.stories';
import { Page } from './Page';
const meta: Meta<typeof Page> = {
title: 'Example/Page',
component: Page,
};
@ -12,9 +13,16 @@ export default meta;
type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = {
args: HeaderStories.LoggedIn.args,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const loginButton = canvas.getByRole('button', { name: /Log in/i });
await expect(loginButton).toBeInTheDocument();
await userEvent.click(loginButton);
// FIXME: await expect(loginButton).not.toBeInTheDocument();
const logoutButton = canvas.getByRole('button', { name: /Log out/i });
await expect(logoutButton).toBeInTheDocument();
},
};
export const LoggedOut: Story = {
args: HeaderStories.LoggedOut.args,
};
export const LoggedOut: Story = {};

View File

@ -1,91 +1,95 @@
import { useState } from 'react';
import { Linking, StyleSheet, Text, View } from 'react-native';
import { Header } from './Header';
export type PageProps = {
user?: {};
onLogin: () => void;
onLogout: () => void;
onCreateAccount: () => void;
};
export const Page = () => {
const [user, setUser] = useState();
export const Page = ({ user, onLogin, onLogout, onCreateAccount }: PageProps) => (
<View>
<Header user={user} onLogin={onLogin} onLogout={onLogout} onCreateAccount={onCreateAccount} />
return (
<View>
<Header
user={user}
onLogin={() => setUser({ name: 'Jane Doe' })}
onLogout={() => setUser(undefined)}
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
/>
<View style={styles.section}>
<Text role="heading" style={styles.h2}>
Pages in Storybook
</Text>
<View style={styles.section}>
<Text role="heading" style={styles.h2}>
Pages in Storybook
</Text>
<Text style={styles.p}>
We recommend building UIs with a{' '}
<Text
style={[styles.a, { fontWeight: 'bold' }]}
role="link"
onPress={() => {
Linking.openURL('https://componentdriven.org');
}}
>
<Text>component-driven</Text>
</Text>{' '}
process starting with atomic components and ending with pages.
</Text>
<Text style={styles.p}>
We recommend building UIs with a{' '}
<Text
style={[styles.a, { fontWeight: 'bold' }]}
role="link"
onPress={() => {
Linking.openURL('https://componentdriven.org');
}}
>
<Text>component-driven</Text>
</Text>{' '}
process starting with atomic components and ending with pages.
</Text>
<Text style={styles.p}>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page data
in Storybook:
</Text>
<View>
<View>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</View>
<Text style={styles.p}>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page
data in Storybook:
</Text>
<View>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</View>
</View>
<View>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</View>
<Text style={styles.p}>
Get a guided tutorial on component-driven development at{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/tutorials/');
}}
>
Storybook tutorials
</Text>
. Read more in the{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/docs');
}}
>
docs
</Text>
.
</Text>
<View style={styles.tipWrapper}>
<View style={styles.tip}>
<Text style={styles.tipText}>Tip </Text>
<View>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</View>
</View>
<Text>Adjust the width of the canvas with the </Text>
<Text style={styles.p}>
Get a guided tutorial on component-driven development at{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/tutorials/');
}}
>
Storybook tutorials
</Text>
. Read more in the{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/docs');
}}
>
docs
</Text>
.
</Text>
<Text>Viewports addon in the toolbar</Text>
<View style={styles.tipWrapper}>
<View style={styles.tip}>
<Text style={styles.tipText}>Tip </Text>
</View>
<Text>Adjust the width of the canvas with the </Text>
<Text>Viewports addon in the toolbar</Text>
</View>
</View>
</View>
</View>
);
);
};
const styles = StyleSheet.create({
section: {

View File

@ -1,10 +1,12 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { View } from 'react-native';
import { Button } from './Button';
const meta = {
title: 'Example/Button',
component: Button,
decorators: [
(Story) => (
@ -15,6 +17,8 @@ const meta = {
],
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// Use `fn` to spy on the onPress arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onPress: fn() },
} satisfies Meta<typeof Button>;
export default meta;

View File

@ -3,6 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Header } from './Header';
const meta = {
title: 'Example/Header',
component: Header,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
@ -14,7 +15,9 @@ type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = {
args: {
user: {},
user: {
name: 'Jane Doe',
},
onLogin: () => {},
onLogout: () => {},
onCreateAccount: () => {},

View File

@ -17,7 +17,13 @@ export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps
</View>
<View style={styles.buttonContainer}>
{user ? (
<Button style={styles.button} size="small" onPress={onLogout} label="Log out" />
<>
<>
<Text>Welcome, </Text>
<Text style={styles.userName}>{user.name}!</Text>
</>
<Button style={styles.button} size="small" onPress={onLogout} label="Log out" />
</>
) : (
<>
<Button style={styles.button} size="small" onPress={onLogin} label="Log in" />
@ -62,5 +68,9 @@ const styles = StyleSheet.create({
},
buttonContainer: {
flexDirection: 'row',
alignItems: 'center',
},
userName: {
fontWeight: '700',
},
});

View File

@ -1,16 +1,24 @@
import type { Meta } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import * as HeaderStories from './Header.stories';
import { Page } from './Page';
export default {
title: 'Example/Page',
component: Page,
} as Meta<typeof Page>;
export const LoggedIn = {
args: HeaderStories.LoggedIn.args,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const loginButton = canvas.getByRole('button', { name: /Log in/i });
await expect(loginButton).toBeInTheDocument();
await userEvent.click(loginButton);
// FIXME: await expect(loginButton).not.toBeInTheDocument();
const logoutButton = canvas.getByRole('button', { name: /Log out/i });
await expect(logoutButton).toBeInTheDocument();
},
};
export const LoggedOut = {
args: HeaderStories.LoggedOut.args,
};
export const LoggedOut = {};

View File

@ -1,83 +1,87 @@
import { useState } from 'react';
import { Linking, StyleSheet, Text, View } from 'react-native';
import { Header } from './Header';
export type PageProps = {
user?: {};
onLogin: () => void;
onLogout: () => void;
onCreateAccount: () => void;
};
export const Page = () => {
const [user, setUser] = useState();
export const Page = ({ user, onLogin, onLogout, onCreateAccount }: PageProps) => (
<View>
<Header user={user} onLogin={onLogin} onLogout={onLogout} onCreateAccount={onCreateAccount} />
return (
<View>
<Header
user={user}
onLogin={() => setUser({ name: 'Jane Doe' })}
onLogout={() => setUser(undefined)}
onCreateAccount={() => setUser({ name: 'Jane Doe' })}
/>
<View style={styles.section}>
<Text role="heading" style={styles.h2}>
Pages in Storybook
</Text>
<Text style={styles.p}>
We recommend building UIs with a{' '}
<Text
style={[styles.a, { fontWeight: 'bold' }]}
role="link"
onPress={() => {
Linking.openURL('https://componentdriven.org');
}}
>
<Text>component-driven</Text>
</Text>{' '}
process starting with atomic components and ending with pages.
</Text>
<Text style={styles.p}>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page data
in Storybook:
</Text>
<View>
<View>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</View>
<View>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</View>
</View>
<Text style={styles.p}>
Get a guided tutorial on component-driven development at{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/tutorials/');
}}
>
Storybook tutorials
<View style={styles.section}>
<Text role="heading" style={styles.h2}>
Pages in Storybook
</Text>
. Read more in the{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/docs');
}}
>
docs
<Text style={styles.p}>
We recommend building UIs with a{' '}
<Text
style={[styles.a, { fontWeight: 'bold' }]}
role="link"
onPress={() => {
Linking.openURL('https://componentdriven.org');
}}
>
<Text>component-driven</Text>
</Text>{' '}
process starting with atomic components and ending with pages.
</Text>
.
</Text>
<View style={styles.tipWrapper}>
<View style={styles.tip}>
<Text style={styles.tipText}>Tip </Text>
<Text style={styles.p}>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page
data in Storybook:
</Text>
<View>
<View>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</View>
<View>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</View>
</View>
<Text style={styles.p}>
Get a guided tutorial on component-driven development at{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/tutorials/');
}}
>
Storybook tutorials
</Text>
. Read more in the{' '}
<Text
style={styles.a}
role="link"
onPress={() => {
Linking.openURL('https://storybook.js.org/docs');
}}
>
docs
</Text>
.
</Text>
<View style={styles.tipWrapper}>
<View style={styles.tip}>
<Text style={styles.tipText}>Tip </Text>
</View>
<Text>Adjust the width of the canvas with the </Text>
<Text>Viewports addon in the toolbar</Text>
</View>
<Text>Adjust the width of the canvas with the </Text>
<Text>Viewports addon in the toolbar</Text>
</View>
</View>
</View>
);
);
};
const styles = StyleSheet.create({
section: {

View File

@ -609,7 +609,7 @@ const baseTemplates = {
renderer: '@storybook/react',
builder: '@storybook/builder-vite',
},
skipTasks: ['bench', 'vitest-integration'],
skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
},
'react-native-web-vite/rn-cli-ts': {
// NOTE: create-expo-app installs React 18.2.0. But yarn portal
@ -628,7 +628,7 @@ const baseTemplates = {
renderer: '@storybook/react',
builder: '@storybook/builder-vite',
},
skipTasks: ['bench', 'vitest-integration'],
skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
},
} satisfies Record<string, BaseTemplates>;
@ -797,8 +797,6 @@ export const normal: TemplateKey[] = [
'bench/react-vite-default-ts-test-build',
'bench/react-webpack-18-ts-test-build',
'ember/default-js',
'react-native-web-vite/expo-ts',
'react-native-web-vite/rn-cli-ts',
];
export const merged: TemplateKey[] = [
@ -831,6 +829,8 @@ export const daily: TemplateKey[] = [
'html-vite/default-js',
'internal/react16-webpack',
'internal/react18-webpack-babel',
'react-native-web-vite/expo-ts',
// 'react-native-web-vite/rn-cli-ts',
];
export const templatesByCadence = { normal, merged, daily };