|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Storybook is used in this project for: |
| 8 | + |
| 9 | +- **Interactive Component Development** - Develop and test payment components in isolation |
| 10 | +- **Visual Testing** - Manually verify component behavior across different SDK versions |
| 11 | +- **Integration Testing** - Automated BrowserStack tests for real browser environments |
| 12 | +- **Documentation** - Living documentation with interactive examples |
| 13 | + |
| 14 | +## Commands |
| 15 | + |
| 16 | +### Development |
| 17 | + |
| 18 | +- `npm run storybook:dev` - Start Storybook development server on port 6006 |
| 19 | +- `npm run storybook:dev-local` - Start Storybook with local build (requires `npm run build` first) |
| 20 | +- `npm run storybook:docs` - Start Storybook in documentation mode |
| 21 | + |
| 22 | +### Building |
| 23 | + |
| 24 | +- `npm run storybook:build` - Build static Storybook site to `storybook-static/` |
| 25 | +- `npm run storybook:run-build` - Serve built Storybook on https://127.0.0.1:8080 |
| 26 | +- `npm run storybook:copy-local-build` - Copy local build to static directory (requires `npm run build` first) |
| 27 | + |
| 28 | +### Testing |
| 29 | + |
| 30 | +- `npm run test:integration` - Run WebDriverIO integration tests on BrowserStack |
| 31 | +- `npm run test:integration:local` - Run tests with local build (`LOCAL_BUILD=true`) |
| 32 | + |
| 33 | +## Architecture |
| 34 | + |
| 35 | +### Directory Structure |
| 36 | + |
| 37 | +``` |
| 38 | +.storybook/ |
| 39 | +├── main.ts # Storybook configuration |
| 40 | +├── preview.ts # Global decorators, loaders, and version toolbar |
| 41 | +├── constants.ts # Shared constants (success messages, test values) |
| 42 | +├── versions.json # List of available SDK versions for toolbar |
| 43 | +├── wdio.conf.ts # WebDriverIO test configuration |
| 44 | +│ |
| 45 | +├── css/ |
| 46 | +│ └── main.css # Shared styles (imported globally in preview.ts) |
| 47 | +│ |
| 48 | +├── stories/ # Story files organized by component |
| 49 | +│ ├── HostedFields/ |
| 50 | +│ ├── PayPalCheckout/ |
| 51 | +│ ├── ApplePay/ |
| 52 | +│ ├── ThreeDSecure/ |
| 53 | +│ ├── VaultManager/ |
| 54 | +│ ├── Venmo/ |
| 55 | +│ └── LocalPaymentMethods/ |
| 56 | +│ |
| 57 | +├── utils/ # Utility functions |
| 58 | +│ ├── BraintreeWebSDKLoader.ts # SDK loading singleton class |
| 59 | +│ ├── story-helper.ts # createSimpleBraintreeStory helper |
| 60 | +│ ├── braintree-globals.ts # URL construction, window.braintree utilities |
| 61 | +│ ├── script-loader.ts # Script tag loading/removal |
| 62 | +│ ├── sdk-config.ts # Version config, authorization token |
| 63 | +│ ├── sdk-metadata.ts # Version metadata management |
| 64 | +│ ├── local-build-manager.ts # Local build detection |
| 65 | +│ ├── version-fetcher.ts # NPM registry version fetching |
| 66 | +│ └── test-data.ts # Test card data |
| 67 | +│ |
| 68 | +├── tests/ # WebDriverIO integration tests |
| 69 | +│ ├── helper.ts # Test helper commands |
| 70 | +│ └── *.test.ts # Test files |
| 71 | +│ |
| 72 | +├── types/ # TypeScript type definitions |
| 73 | +│ |
| 74 | +└── static/local-build/ # Local SDK builds (created by script) |
| 75 | +``` |
| 76 | + |
| 77 | +### SDK Version Management |
| 78 | + |
| 79 | +The Storybook toolbar allows switching between SDK versions: |
| 80 | + |
| 81 | +1. **"dev"** - Local build from `.storybook/static/local-build/` |
| 82 | +2. **Production versions** - CDN-hosted builds (e.g., "3.133.0") |
| 83 | + |
| 84 | +Version selection: |
| 85 | + |
| 86 | +- URL parameter: `?globals=sdkVersion:3.133.0` |
| 87 | +- Storybook globals toolbar |
| 88 | +- Default: `"dev"` (local build) |
| 89 | + |
| 90 | +### SDK Loading Flow |
| 91 | + |
| 92 | +1. **preview.ts loader** - Pre-loads SDK based on selected version and `parameters.braintreeScripts` |
| 93 | +2. **story-helper.ts** - `createSimpleBraintreeStory` ensures required scripts are loaded |
| 94 | +3. **BraintreeWebSDKLoader.ts** - Singleton that manages script loading, version switching, cleanup |
| 95 | + |
| 96 | +## Writing Stories |
| 97 | + |
| 98 | +### Basic Story Structure |
| 99 | + |
| 100 | +```typescript |
| 101 | +import type { Meta, StoryObj } from "@storybook/html"; |
| 102 | +import { createSimpleBraintreeStory } from "../../utils/story-helper"; |
| 103 | +import { getAuthorizationToken } from "../../utils/sdk-config"; |
| 104 | +import "./componentName.css"; |
| 105 | + |
| 106 | +const meta: Meta = { |
| 107 | + title: "Braintree/Component Name", |
| 108 | + parameters: { |
| 109 | + layout: "centered", |
| 110 | + braintreeScripts: ["component-name"], // Scripts for preview loader to pre-load |
| 111 | + docs: { |
| 112 | + description: { |
| 113 | + component: `Component description in markdown`, |
| 114 | + }, |
| 115 | + }, |
| 116 | + }, |
| 117 | +}; |
| 118 | + |
| 119 | +export default meta; |
| 120 | + |
| 121 | +export const BasicExample: StoryObj = { |
| 122 | + render: createSimpleBraintreeStory( |
| 123 | + (container, args) => { |
| 124 | + // Your render code - window.braintree is available |
| 125 | + const authorization = getAuthorizationToken(); |
| 126 | + // ... |
| 127 | + }, |
| 128 | + ["client.min.js", "component-name.min.js"] // Scripts this story needs |
| 129 | + ), |
| 130 | + argTypes: { |
| 131 | + optionName: { |
| 132 | + control: { type: "boolean" }, |
| 133 | + description: "Option description", |
| 134 | + }, |
| 135 | + }, |
| 136 | + args: { |
| 137 | + optionName: true, |
| 138 | + }, |
| 139 | +}; |
| 140 | +``` |
| 141 | + |
| 142 | +### Key Points |
| 143 | + |
| 144 | +1. **`parameters.braintreeScripts`** - Array of script names (without `.min.js`) for the preview loader to pre-load |
| 145 | +2. **`createSimpleBraintreeStory` second arg** - Array of full script filenames the story requires |
| 146 | +3. **`getAuthorizationToken()`** - Returns `import.meta.env.STORYBOOK_BRAINTREE_TOKENIZATION_KEY` |
| 147 | +4. **Shared styles** - Already imported globally via `preview.ts`, no need to import in stories |
| 148 | + |
| 149 | +### Result Display Pattern |
| 150 | + |
| 151 | +```typescript |
| 152 | +// Success |
| 153 | +resultDiv.classList.add("shared-result--visible", "shared-result--success"); |
| 154 | +resultDiv.innerHTML = `<strong>Success!</strong><small>Nonce: ${payload.nonce}</small>`; |
| 155 | + |
| 156 | +// Error |
| 157 | +resultDiv.classList.add("shared-result--visible", "shared-result--error"); |
| 158 | +resultDiv.innerHTML = `<strong>Error:</strong> ${error.message}`; |
| 159 | +``` |
| 160 | + |
| 161 | +### Test Data |
| 162 | + |
| 163 | +```typescript |
| 164 | +import { TEST_CARDS } from "../../utils/test-data"; |
| 165 | + |
| 166 | +// Cards available: visa, mastercard, amex, discover, etc. |
| 167 | +const cardData = TEST_CARDS.visa; |
| 168 | +// { number: "4111111111111111", cvv: "123", expirationDate: "MM/YY", postalCode: "12345" } |
| 169 | +``` |
| 170 | + |
| 171 | +## Integration Testing |
| 172 | + |
| 173 | +### Test Configuration |
| 174 | + |
| 175 | +- **Framework:** Mocha with WebDriverIO |
| 176 | +- **Browsers:** Chrome (Windows 10), Safari (macOS Monterey) |
| 177 | +- **Base URL:** `https://127.0.0.1:8080` |
| 178 | + |
| 179 | +### Custom Browser Commands |
| 180 | + |
| 181 | +Defined in `tests/helper.ts`: |
| 182 | + |
| 183 | +- `browser.waitForHostedFieldsReady()` - Wait for SDK and all hosted field iframes |
| 184 | +- `browser.waitForHostedField(key)` - Wait for specific hosted field |
| 185 | +- `browser.hostedFieldSendInput(key, value)` - Type into hosted field iframe |
| 186 | +- `browser.waitForFormReady()` - Wait for submit button to be enabled |
| 187 | +- `browser.submitPay()` - Submit form and wait for result |
| 188 | +- `browser.getResult()` - Extract success/failure from result div |
| 189 | +- `getWorkflowUrl(path)` - Build story URL with version param |
| 190 | + |
| 191 | +### Writing Tests |
| 192 | + |
| 193 | +```typescript |
| 194 | +import { browser, $ } from "@wdio/globals"; |
| 195 | +import { expect } from "chai"; |
| 196 | +import { getWorkflowUrl, loadHelpers } from "./helper"; |
| 197 | + |
| 198 | +describe("Component Integration", () => { |
| 199 | + before(() => loadHelpers()); |
| 200 | + |
| 201 | + it("should complete flow", async () => { |
| 202 | + const url = getWorkflowUrl( |
| 203 | + "/iframe.html?id=braintree-component--story-name" |
| 204 | + ); |
| 205 | + await browser.url(url); |
| 206 | + |
| 207 | + await browser.waitForHostedFieldsReady(); |
| 208 | + await browser.hostedFieldSendInput("number", "4111111111111111"); |
| 209 | + // ... |
| 210 | + |
| 211 | + const result = await browser.getResult(); |
| 212 | + expect(result.success).to.be.true; |
| 213 | + }); |
| 214 | +}); |
| 215 | +``` |
| 216 | + |
| 217 | +## Environment Variables |
| 218 | + |
| 219 | +Required in `.env`: |
| 220 | + |
| 221 | +```bash |
| 222 | +STORYBOOK_BRAINTREE_TOKENIZATION_KEY=sandbox_xxxxx_yyyyyy |
| 223 | + |
| 224 | +# For integration tests only: |
| 225 | +BROWSERSTACK_USERNAME=your_username |
| 226 | +BROWSERSTACK_ACCESS_KEY=your_access_key |
| 227 | +``` |
| 228 | + |
| 229 | +## Using Local Builds |
| 230 | + |
| 231 | +```bash |
| 232 | +npm run build # Build SDK |
| 233 | +npm run storybook:copy-local-build # Copy to static directory |
| 234 | +npm run storybook:dev-local # Start with local build |
| 235 | +``` |
| 236 | + |
| 237 | +Or combined: `npm run storybook:dev-local` (runs copy-local-build first) |
| 238 | + |
| 239 | +## Troubleshooting |
| 240 | + |
| 241 | +### Local Build Not Appearing |
| 242 | + |
| 243 | +1. Run `npm run build` |
| 244 | +2. Run `npm run storybook:copy-local-build` |
| 245 | +3. Check `.storybook/static/local-build/js/` for `.js` files |
| 246 | + |
| 247 | +### SDK Loading Errors |
| 248 | + |
| 249 | +1. Check browser console for script loading errors |
| 250 | +2. Verify `STORYBOOK_BRAINTREE_TOKENIZATION_KEY` in `.env` |
| 251 | +3. Try switching to a CDN version in toolbar |
| 252 | +4. For local builds, ensure build was run first |
| 253 | + |
| 254 | +### Version Switching Issues |
| 255 | + |
| 256 | +1. Hard refresh (Cmd+Shift+R / Ctrl+Shift+R) |
| 257 | +2. Check URL has correct `globals` parameter |
| 258 | +3. Check console for version selection logs |
0 commit comments