mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-11-02 15:28:09 +00:00
* fix(ci): Fix web Storybook screenshots and add mobile support ## Web Storybook Screenshots Fixes - Fix story ID generation to use `title` from meta export instead of file paths - Previously generated wrong IDs like `features-portfolio-components-x` - Now correctly generates `features-portfolio-x` matching Storybook's IDs - Fix git credentials for pushing to storage branch - Was failing with "could not read Username" error - Now uses same working credential helper as page-screenshots workflow ## Mobile Storybook Screenshots (New) Add new workflow to capture and post mobile component screenshots to PRs: - Triggers on changes to `apps/mobile/src/**/*.stories.tsx` - Builds mobile Storybook for web using React Native Web - Serves locally and captures with Playwright (iPhone 14 Pro viewport) - Posts screenshots to PR with 📱 Mobile Storybook Screenshots heading New files: - .github/workflows/mobile-storybook-screenshots.yml - .github/scripts/generate-mobile-story-urls.js - .github/scripts/capture-mobile-screenshots.js * test: Add test changes to trigger screenshot workflows - Add title to web CopyButton story (was missing explicit title) - Add layout parameter to mobile SafeButton story These changes test the screenshot workflows on this PR. * fix(ci): Add path traversal protection to mobile screenshot server Prevent potential path traversal attacks by: - Resolving and normalizing the static directory path - Validating that resolved file paths stay within the static directory - Returning 403 Forbidden for any path traversal attempts * fix(ci): Fix mobile Storybook build and add security fixes - Run build from apps/mobile directory to fix monorepo path resolution - Add --disable-telemetry flag to avoid telemetry module resolution error - Add path traversal protection to static file server * fix(ci): Add graceful handling for mobile Storybook build failures The mobile Storybook build has a known issue with monorepo path resolution for telemetry metadata. This update: - Makes the build step continue on error with a warning - Skips screenshot capture when build fails - Prevents workflow failure when Storybook build issues occur * fix(ci): Address Copilot review suggestions - Fix regex pattern for title extraction to handle quotes properly (handles titles with apostrophes like "It's working") - Fix binary content encoding in static server (don't use utf-8 for images/fonts) - Filter out error screenshots from PR comments - Use greedy regex matching for component names with hyphens * chore: Remove test story changes Revert test changes made to trigger screenshot workflows: - Remove title from CopyButton story - Remove layout parameter from SafeButton story * refactor: Remove web storybook fixes (moved to separate PR) The web storybook screenshot fixes have been extracted to PR #6870. This PR now only contains the new mobile storybook screenshots feature. * chore(mobile): Fix theme type errors and trigger mobile screenshots workflow - Add optional chaining to theme property access throughout mobile app - Fixes TypeScript errors where theme properties were possibly undefined - Add comment to SafeButton story to trigger mobile screenshots workflow * fix(mobile): Disable Storybook telemetry to fix monorepo build Add core.disableTelemetry config to prevent telemetry module from attempting to resolve framework packages in monorepo structure, which was causing the build to fail. * fix(mobile): Use standard framework config instead of path resolution Replace path resolution workaround with standard framework configuration to fix 'Cannot find module' error in CI environment. * fix(ci): Access Storybook iframe for mobile screenshots Storybook renders stories inside an iframe, so we need to: 1. Wait for the iframe to load 2. Access the iframe's content frame 3. Look for #storybook-root inside the iframe This fixes the 'element is hidden' error in screenshot capture. * fix(ci): Add comprehensive mocks for mobile Storybook web build Fix mobile Storybook rendering by mocking React Native modules that don't work in web browsers. Changes: - Mock react-native-worklets with version 0.5.0 and all required functions - Create standalone mock for react-native-reanimated (hooks, animations, utilities) - Mock react-refresh to prevent production bundle errors - Mock react-native-quick-crypto using Node.js crypto - Update capture script to wait for #storybook-root to be visible - Use webpack aliases to replace native modules with mocks The mobile Storybook now successfully renders components in a web browser and screenshots can be captured with Playwright. * test: Trigger mobile screenshots workflow Trigger the mobile screenshots workflow by modifying a story file. * fix(ci): Set STORYBOOK_WEB=true for mobile Storybook build The build needs STORYBOOK_WEB=true to use the web configuration with mocks. Without this, it tries to build the native Storybook which doesn't have the webpack aliases for React Native module mocks. * fix(ci): Handle direct iframe.html navigation in screenshot capture The script was looking for iframe#storybook-preview-iframe, but story URLs navigate directly to iframe.html which renders content without the wrapper. Changes: - Detect if URL contains 'iframe.html' and use page directly - Only look for iframe wrapper when on main Storybook page - Simplify content selection to work with direct iframe rendering Tested locally - all 5 screenshots now capture successfully. --------- Co-authored-by: Hanzo Dev <[email protected]>
131 lines
3.2 KiB
JavaScript
131 lines
3.2 KiB
JavaScript
/* eslint-disable */
|
|
// Standalone mock for react-native-reanimated in Storybook
|
|
// Based on the official mock but self-contained
|
|
|
|
const NOOP = () => {};
|
|
const NOOP_FACTORY = () => NOOP;
|
|
const ID = (t) => t;
|
|
const IMMEDIATE_CALLBACK_INVOCATION = (callback) => callback();
|
|
|
|
// Mock hooks
|
|
const hook = {
|
|
useAnimatedProps: IMMEDIATE_CALLBACK_INVOCATION,
|
|
useEvent: () => NOOP,
|
|
useSharedValue: (init) => {
|
|
const value = { value: init };
|
|
return new Proxy(value, {
|
|
get(target, prop) {
|
|
if (prop === 'value') return target.value;
|
|
if (prop === 'get') return () => target.value;
|
|
if (prop === 'set') {
|
|
return (newValue) => {
|
|
if (typeof newValue === 'function') {
|
|
target.value = newValue(target.value);
|
|
} else {
|
|
target.value = newValue;
|
|
}
|
|
};
|
|
}
|
|
},
|
|
set(target, prop, newValue) {
|
|
if (prop === 'value') {
|
|
target.value = newValue;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
},
|
|
useAnimatedStyle: IMMEDIATE_CALLBACK_INVOCATION,
|
|
useAnimatedReaction: NOOP,
|
|
useAnimatedRef: () => ({ current: null }),
|
|
useAnimatedScrollHandler: NOOP_FACTORY,
|
|
useDerivedValue: (processor) => {
|
|
const result = processor();
|
|
return { value: result, get: () => result };
|
|
},
|
|
useAnimatedSensor: () => ({
|
|
sensor: { value: { x: 0, y: 0, z: 0 } },
|
|
unregister: NOOP,
|
|
isAvailable: false,
|
|
config: { interval: 0 },
|
|
}),
|
|
useAnimatedKeyboard: () => ({ height: 0, state: 0 }),
|
|
useScrollViewOffset: () => ({ value: 0 }),
|
|
useScrollOffset: () => ({ value: 0 }),
|
|
};
|
|
|
|
// Mock animation functions
|
|
const animation = {
|
|
cancelAnimation: NOOP,
|
|
withDecay: (_config, callback) => {
|
|
callback?.(true);
|
|
return 0;
|
|
},
|
|
withDelay: (_delayMs, nextAnimation) => nextAnimation,
|
|
withRepeat: ID,
|
|
withSequence: () => 0,
|
|
withSpring: (toValue, _config, callback) => {
|
|
callback?.(true);
|
|
return toValue;
|
|
},
|
|
withTiming: (toValue, _config, callback) => {
|
|
callback?.(true);
|
|
return toValue;
|
|
},
|
|
};
|
|
|
|
// Mock utilities
|
|
const utilities = {
|
|
runOnUI: (fn) => (...args) => fn(...args),
|
|
runOnJS: (fn) => (...args) => fn(...args),
|
|
interpolate: (value) => value,
|
|
interpolateColor: (value) => value,
|
|
Easing: {
|
|
linear: ID,
|
|
ease: ID,
|
|
quad: ID,
|
|
cubic: ID,
|
|
},
|
|
Extrapolate: {
|
|
EXTEND: 'extend',
|
|
CLAMP: 'clamp',
|
|
IDENTITY: 'identity',
|
|
},
|
|
};
|
|
|
|
// Animated components (passthrough to regular React Native components)
|
|
const { Animated, View, Text, ScrollView, Image } = require('react-native');
|
|
|
|
const AnimatedView = View;
|
|
const AnimatedText = Text;
|
|
const AnimatedScrollView = ScrollView;
|
|
const AnimatedImage = Image;
|
|
|
|
// Create animated component wrapper
|
|
const createAnimatedComponent = (Component) => Component;
|
|
|
|
// Export everything
|
|
module.exports = {
|
|
...hook,
|
|
...animation,
|
|
...utilities,
|
|
createAnimatedComponent,
|
|
default: {
|
|
View: AnimatedView,
|
|
Text: AnimatedText,
|
|
ScrollView: AnimatedScrollView,
|
|
Image: AnimatedImage,
|
|
createAnimatedComponent,
|
|
...hook,
|
|
...animation,
|
|
...utilities,
|
|
},
|
|
// Animated components
|
|
View: AnimatedView,
|
|
Text: AnimatedText,
|
|
ScrollView: AnimatedScrollView,
|
|
Image: AnimatedImage,
|
|
__esModule: true,
|
|
};
|