|
1 | 1 | import {buildFeedKeysWithAssignedCards, isExpensifyCardUkEuSupportedSelector} from '@selectors/Card'; |
| 2 | +import * as fs from 'fs'; |
2 | 3 | import lodashSortBy from 'lodash/sortBy'; |
| 4 | +import * as path from 'path'; |
3 | 5 | import type {OnyxCollection} from 'react-native-onyx'; |
4 | 6 | import type {LocalizedTranslate} from '@components/LocaleContextProvider'; |
5 | 7 | import type {FeedKeysWithAssignedCards} from '@hooks/useFeedKeysWithAssignedCards'; |
6 | 8 | import type IllustrationsType from '@styles/theme/illustrations/types'; |
7 | 9 | import CONST from '@src/CONST'; |
8 | 10 | import type {CombinedCardFeeds} from '@src/hooks/useCardFeeds'; |
9 | 11 | import IntlStore from '@src/languages/IntlStore'; |
| 12 | +import type * as CardArtworkColorsModule from '@src/libs/CardArtworkColors'; |
10 | 13 | import { |
11 | 14 | doesCardFeedExist, |
12 | 15 | feedHasCards, |
@@ -4362,3 +4365,92 @@ describe('getConnectionBankAccountsForReconciliation', () => { |
4362 | 4365 | expect(getConnectionBankAccountsForReconciliation(connections, CONST.POLICY.CONNECTIONS.NAME.QBO)).toEqual([]); |
4363 | 4366 | }); |
4364 | 4367 | }); |
| 4368 | + |
| 4369 | +/** |
| 4370 | + * Drift-detection: verifies that the background colors in src/libs/CardArtworkColors.ts |
| 4371 | + * still match what the SVG artwork files actually contain. If a card SVG is updated without |
| 4372 | + * updating CardArtworkColors.ts, this test will fail with a clear message pointing to the |
| 4373 | + * affected entry. |
| 4374 | + */ |
| 4375 | +describe('CardArtworkColors drift detection', () => { |
| 4376 | + const ROOT = path.resolve(__dirname, '../..'); |
| 4377 | + |
| 4378 | + /** Inline copy of the parser from scripts/generateCardColors.ts */ |
| 4379 | + function normalizeHex(color: string): string { |
| 4380 | + const h = color.trim().toLowerCase(); |
| 4381 | + if (/^#[0-9a-f]{3}$/.test(h)) { |
| 4382 | + return `#${h[1]}${h[1]}${h[2]}${h[2]}${h[3]}${h[3]}`; |
| 4383 | + } |
| 4384 | + return h; |
| 4385 | + } |
| 4386 | + |
| 4387 | + function extractBackgroundFill(svgContent: string): string | null { |
| 4388 | + const styleMap: Record<string, string> = {}; |
| 4389 | + const styleMatch = svgContent.match(/<style>([\s\S]*?)<\/style>/); |
| 4390 | + if (styleMatch) { |
| 4391 | + for (const m of styleMatch[1].matchAll(/\.([\w-]+)\s*\{([^}]+)\}/g)) { |
| 4392 | + const fillMatch = m[2].match(/\bfill:\s*([#\w(),.%]+)/); |
| 4393 | + if (fillMatch) { |
| 4394 | + styleMap[m[1]] = fillMatch[1].trim(); |
| 4395 | + } |
| 4396 | + } |
| 4397 | + } |
| 4398 | + const withoutDefs = svgContent.replaceAll(/<defs>[\s\S]*?<\/defs>/g, ''); |
| 4399 | + const tagRegex = /<(?:rect|path)\s[^>]*?\/?>/g; |
| 4400 | + let match: RegExpExecArray | null; |
| 4401 | + // eslint-disable-next-line no-cond-assign |
| 4402 | + while ((match = tagRegex.exec(withoutDefs)) !== null) { |
| 4403 | + const tag = match[0]; |
| 4404 | + const fillAttr = tag.match(/\bfill="([^"]+)"/)?.[1]; |
| 4405 | + const classAttr = tag.match(/\bclass="([^"]+)"/)?.[1]; |
| 4406 | + if (fillAttr && fillAttr !== 'none') { |
| 4407 | + return normalizeHex(fillAttr); |
| 4408 | + } |
| 4409 | + if (classAttr) { |
| 4410 | + for (const cn of classAttr.split(/\s+/)) { |
| 4411 | + const resolved = styleMap[cn]; |
| 4412 | + if (resolved && resolved !== 'none') { |
| 4413 | + return normalizeHex(resolved); |
| 4414 | + } |
| 4415 | + } |
| 4416 | + } |
| 4417 | + } |
| 4418 | + return null; |
| 4419 | + } |
| 4420 | + |
| 4421 | + const FEED_ARTWORK: Array<{keys: string[]; svgPath: string}> = [ |
| 4422 | + {keys: ['Expensify Card'], svgPath: 'assets/images/expensify-card.svg'}, |
| 4423 | + {keys: ['vcf'], svgPath: 'assets/images/companyCards/large/card-visa-large.svg'}, |
| 4424 | + {keys: ['cdf'], svgPath: 'assets/images/companyCards/large/card-mastercard-large.svg'}, |
| 4425 | + { |
| 4426 | + keys: ['gl1025', 'gl1205', 'oauth.americanexpressfdx.com', 'americanexpressfd.us'], |
| 4427 | + svgPath: 'assets/images/companyCards/large/card-amex-large.svg', |
| 4428 | + }, |
| 4429 | + {keys: ['oauth.bankofamerica.com'], svgPath: 'assets/images/companyCards/large/card-bofa-large.svg'}, |
| 4430 | + {keys: ['oauth.capitalone.com'], svgPath: 'assets/images/companyCards/large/card-capital_one-large.svg'}, |
| 4431 | + {keys: ['oauth.chase.com'], svgPath: 'assets/images/companyCards/large/card-chase-large.svg'}, |
| 4432 | + {keys: ['oauth.citibank.com'], svgPath: 'assets/images/companyCards/large/card-citi-large.svg'}, |
| 4433 | + {keys: ['oauth.wellsfargo.com'], svgPath: 'assets/images/companyCards/large/card-wellsfargo-large.svg'}, |
| 4434 | + {keys: ['oauth.brex.com'], svgPath: 'assets/images/companyCards/large/card-brex-large.svg'}, |
| 4435 | + {keys: ['stripe'], svgPath: 'assets/images/companyCards/large/card-stripe-large.svg'}, |
| 4436 | + {keys: ['plaid'], svgPath: 'assets/images/companyCards/large/card-plaid-large.svg'}, |
| 4437 | + ]; |
| 4438 | + |
| 4439 | + const GENERIC_SVG_PATH = 'assets/images/companyCards/large/generic-light-large.svg'; |
| 4440 | + |
| 4441 | + it('GENERIC_CARD_COLORS.background matches generic-light-large.svg', () => { |
| 4442 | + const {GENERIC_CARD_COLORS} = jest.requireActual<typeof CardArtworkColorsModule>('@src/libs/CardArtworkColors'); |
| 4443 | + const svg = fs.readFileSync(path.join(ROOT, GENERIC_SVG_PATH), 'utf-8'); |
| 4444 | + const actual = extractBackgroundFill(svg); |
| 4445 | + expect(actual).not.toBeNull(); |
| 4446 | + expect(GENERIC_CARD_COLORS.background).toBe(actual); |
| 4447 | + }); |
| 4448 | + |
| 4449 | + it.each(FEED_ARTWORK.flatMap(({keys, svgPath}) => keys.map((key) => ({key, svgPath}))))('CARD_FEED_COLORS[$key].background matches $svgPath', ({key, svgPath}) => { |
| 4450 | + const {CARD_FEED_COLORS} = jest.requireActual<typeof CardArtworkColorsModule>('@src/libs/CardArtworkColors'); |
| 4451 | + const svg = fs.readFileSync(path.join(ROOT, svgPath), 'utf-8'); |
| 4452 | + const actual = extractBackgroundFill(svg); |
| 4453 | + expect(actual).not.toBeNull(); |
| 4454 | + expect(CARD_FEED_COLORS[key].background).toBe(actual); |
| 4455 | + }); |
| 4456 | +}); |
0 commit comments