@onekeyfe/react-native-native-list 6.7.0-alpha.411 → 6.7.0-alpha.412

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +5 -0
  2. package/android/src/main/java/com/onekey/nativelist/NativeListGeneratedIcons.kt +47 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +5 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +117 -338
  5. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +5 -0
  6. package/docs/SPEC.md +114 -7
  7. package/ios/NativeListAccessoryStack.swift +6 -16
  8. package/ios/NativeListDataRowRenderer.swift +15 -6
  9. package/ios/NativeListDesignAssets.swift +13 -84
  10. package/ios/NativeListGeneratedIcons.swift +55 -0
  11. package/ios/NativeListIconBinding.swift +171 -0
  12. package/ios/NativeListIdentityRenderer.swift +12 -10
  13. package/ios/NativeListLeadingVisual.swift +11 -9
  14. package/ios/NativeListMarketRenderer.swift +7 -13
  15. package/ios/NativeListMediaTileRenderer.swift +6 -2
  16. package/ios/NativeListMetricCardRenderer.swift +4 -2
  17. package/ios/NativeListSectionHeaderRenderer.swift +12 -4
  18. package/ios/tests/NativeListAccessoryStackTests.swift +12 -0
  19. package/ios/tests/NativeListSharedIconTests.swift +236 -0
  20. package/lib/module/web/NativeListGeneratedIcons.js +31 -0
  21. package/lib/module/web/NativeListWebEngine.js +21 -323
  22. package/lib/module/web/templates/ActivityRowRenderer.js +4 -24
  23. package/lib/module/web/templates/MarketTableRowRenderer.js +11 -15
  24. package/lib/module/web/templates/MediaTileRowRenderer.js +2 -12
  25. package/lib/typescript/src/web/NativeListGeneratedIcons.d.ts +2 -0
  26. package/lib/typescript/src/web/NativeListWebEngine.d.ts +0 -1
  27. package/package.json +10 -4
  28. package/react-native-native-list.podspec +0 -5
  29. package/scripts/generate-icons.mjs +150 -0
  30. package/scripts/generate-icons.test.mjs +288 -0
  31. package/scripts/native-list-icons.json +260 -0
  32. package/src/web/NativeListGeneratedIcons.ts +28 -0
  33. package/src/web/NativeListWebEngine.ts +25 -405
  34. package/src/web/templates/ActivityRowRenderer.ts +15 -53
  35. package/src/web/templates/MarketTableRowRenderer.ts +17 -29
  36. package/src/web/templates/MediaTileRowRenderer.ts +2 -15
  37. package/ios/Resources/NativeListIcons.xcassets/Contents.json +0 -1
  38. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/Contents.json +0 -1
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/icon.svg +0 -1
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/Contents.json +0 -1
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/icon.svg +0 -1
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/Contents.json +0 -1
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/icon.svg +0 -1
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/Contents.json +0 -1
  45. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/icon.svg +0 -1
  46. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/Contents.json +0 -1
  47. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/icon.svg +0 -1
  48. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/Contents.json +0 -1
  49. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/icon.svg +0 -1
  50. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/Contents.json +0 -1
  51. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/icon.svg +0 -1
  52. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/Contents.json +0 -1
  53. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/icon.svg +0 -1
  54. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/Contents.json +0 -1
  55. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/icon.svg +0 -1
  56. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/Contents.json +0 -1
  57. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/icon.svg +0 -1
  58. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +0 -15
  59. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +0 -4
  60. package/ios/Resources/NativeListIcons.xcassets/onekey_document2.imageset/Contents.json +0 -1
  61. package/ios/Resources/NativeListIcons.xcassets/onekey_document2.imageset/icon.svg +0 -1
  62. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/Contents.json +0 -1
  63. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/icon.svg +0 -1
  64. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_ver_solid.imageset/Contents.json +0 -1
  65. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_ver_solid.imageset/icon.svg +0 -1
  66. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/Contents.json +0 -1
  67. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/icon.svg +0 -1
  68. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/Contents.json +0 -1
  69. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/icon.svg +0 -1
  70. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +0 -15
  71. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +0 -3
  72. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/Contents.json +0 -1
  73. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/icon.svg +0 -1
  74. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/Contents.json +0 -1
  75. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/icon.svg +0 -1
  76. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/Contents.json +0 -1
  77. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/icon.svg +0 -1
  78. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/Contents.json +0 -1
  79. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/icon.svg +0 -1
  80. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/Contents.json +0 -1
  81. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/icon.svg +0 -1
  82. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/Contents.json +0 -1
  83. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/icon.svg +0 -1
  84. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_large.imageset/Contents.json +0 -1
  85. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_large.imageset/icon.svg +0 -1
  86. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/Contents.json +0 -1
  87. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/icon.svg +0 -1
  88. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/Contents.json +0 -1
  89. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/icon.svg +0 -1
  90. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +0 -15
  91. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +0 -3
  92. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/Contents.json +0 -15
  93. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/icon.svg +0 -14
  94. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/Contents.json +0 -15
  95. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/icon.svg +0 -12
  96. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/Contents.json +0 -15
  97. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/icon.svg +0 -6
  98. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/Contents.json +0 -15
  99. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/icon.svg +0 -15
  100. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/Contents.json +0 -15
  101. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/icon.svg +0 -3
  102. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/Contents.json +0 -15
  103. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/icon.svg +0 -7
  104. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/Contents.json +0 -15
  105. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/icon.svg +0 -18
  106. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/Contents.json +0 -15
  107. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/icon.svg +0 -7
  108. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/Contents.json +0 -1
  109. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/icon.svg +0 -1
  110. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/Contents.json +0 -1
  111. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/icon.svg +0 -1
  112. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/Contents.json +0 -1
  113. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/icon.svg +0 -1
  114. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/Contents.json +0 -1
  115. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/icon.svg +0 -1
  116. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/Contents.json +0 -1
  117. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/icon.svg +0 -1
  118. package/ios/Resources/NativeListIcons.xcassets/onekey_ticket.imageset/Contents.json +0 -1
  119. package/ios/Resources/NativeListIcons.xcassets/onekey_ticket.imageset/icon.svg +0 -1
  120. package/ios/Resources/NativeListIcons.xcassets/onekey_wallet.imageset/Contents.json +0 -1
  121. package/ios/Resources/NativeListIcons.xcassets/onekey_wallet.imageset/icon.svg +0 -1
@@ -0,0 +1,150 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7
+ const require = createRequire(import.meta.url);
8
+ const prettier = require('prettier');
9
+ const format = JSON.parse(
10
+ fs.readFileSync(path.join(root, 'package.json'), 'utf8')
11
+ ).prettier;
12
+ const header =
13
+ '// Generated by scripts/generate-icons.mjs. Coverage and legacy names only; hosts provide SVG resources.\n';
14
+ export function generateOutputs(
15
+ fixtureRoot = path.resolve(
16
+ root,
17
+ '../../example/react-native/icon-fixtures/native/icons'
18
+ ),
19
+ outputRoot = root
20
+ ) {
21
+ const entries = JSON.parse(
22
+ fs.readFileSync(path.join(root, 'scripts/native-list-icons.json'), 'utf8')
23
+ );
24
+ const catalog = JSON.parse(
25
+ fs.readFileSync(path.join(fixtureRoot, 'manifest.json'), 'utf8')
26
+ );
27
+ const records = resolveRecords(entries, catalog, fixtureRoot);
28
+ return renderOutputs(records, outputRoot);
29
+ }
30
+ export function resolveRecords(entries, catalog, fixtureRoot) {
31
+ const names = new Set();
32
+ if (!Array.isArray(entries)) throw new Error('Invalid icon manifest');
33
+ return entries
34
+ .map((entry) => {
35
+ if (
36
+ !entry ||
37
+ typeof entry.name !== 'string' ||
38
+ !/^[A-Z][A-Za-z0-9]*$/.test(entry.name) ||
39
+ names.has(entry.name) ||
40
+ !Array.isArray(entry.platforms) ||
41
+ !entry.platforms.length ||
42
+ new Set(entry.platforms).size !== entry.platforms.length ||
43
+ entry.platforms.some((p) => !['ios', 'android', 'web'].includes(p)) ||
44
+ !['template', 'original', 'primitive'].includes(entry.rendering)
45
+ )
46
+ throw new Error('Invalid icon manifest entry');
47
+ names.add(entry.name);
48
+ if (entry.name === 'Circle')
49
+ return { ...entry, canonical: 'Circle', width: 24, height: 24 };
50
+ if (
51
+ typeof entry.source !== 'string' ||
52
+ entry.source.includes('..') ||
53
+ path.isAbsolute(entry.source)
54
+ )
55
+ throw new Error('Invalid icon manifest entry');
56
+ const matches = Object.entries(catalog).filter(
57
+ ([, value]) => value.source === entry.source
58
+ );
59
+ if (matches.length > 1)
60
+ throw new Error(`Ambiguous shared SVG source: ${entry.source}`);
61
+ const match = matches[0];
62
+ if (!match) throw new Error(`Missing shared SVG source: ${entry.source}`);
63
+ const [canonical, metadata] = match;
64
+ const source = fs.readFileSync(
65
+ path.join(fixtureRoot, `${canonical}.svg`),
66
+ 'utf8'
67
+ );
68
+ const raw = source.endsWith('\n') ? source : source + '\n';
69
+ if (
70
+ crypto.createHash('sha256').update(raw).digest('hex') !==
71
+ metadata.digest
72
+ )
73
+ throw new Error(`Shared SVG resources stale: ${entry.source}`);
74
+ return {
75
+ ...entry,
76
+ canonical,
77
+ width: metadata.viewBox[2],
78
+ height: metadata.viewBox[3],
79
+ };
80
+ })
81
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
82
+ }
83
+ function renderOutputs(records, outputRoot) {
84
+ const swift =
85
+ header +
86
+ 'import UIKit\n\nstruct NativeListIconMetadata {\n let name: String\n let size: CGSize\n let original: Bool\n}\n\nenum NativeListGeneratedIcons {\n static let icons: [String: NativeListIconMetadata] = [\n' +
87
+ records
88
+ .filter((x) => x.platforms.includes('ios'))
89
+ .map(
90
+ (x) =>
91
+ ` "${x.name}": NativeListIconMetadata(name: "${
92
+ x.canonical
93
+ }", size: CGSize(width: ${x.width}, height: ${x.height}), original: ${
94
+ x.rendering === 'original'
95
+ }),`
96
+ )
97
+ .join('\n') +
98
+ '\n ]\n}\n';
99
+ const kotlin =
100
+ header +
101
+ 'package com.margelo.nitro.nativelist\n\ninternal data class NativeListIconMetadata(val name: String, val width: Float, val height: Float)\ninternal val nativeListGeneratedIcons: Map<String, NativeListIconMetadata> = mapOf(\n' +
102
+ records
103
+ .filter((x) => x.platforms.includes('android'))
104
+ .map(
105
+ (x) =>
106
+ ` "${x.name}" to NativeListIconMetadata("${x.canonical}", ${x.width}f, ${x.height}f),`
107
+ )
108
+ .join('\n') +
109
+ '\n)\n';
110
+ const web =
111
+ header +
112
+ 'export const nativeListGeneratedIcons: Readonly<Record<string, string>> = ' +
113
+ JSON.stringify(
114
+ Object.fromEntries(
115
+ records
116
+ .filter((x) => x.platforms.includes('web'))
117
+ .map((x) => [x.name, x.canonical])
118
+ )
119
+ ) +
120
+ ';\n';
121
+ return new Map([
122
+ [path.join(outputRoot, 'ios/NativeListGeneratedIcons.swift'), swift],
123
+ [
124
+ path.join(
125
+ outputRoot,
126
+ 'android/src/main/java/com/onekey/nativelist/NativeListGeneratedIcons.kt'
127
+ ),
128
+ kotlin,
129
+ ],
130
+ [
131
+ path.join(outputRoot, 'src/web/NativeListGeneratedIcons.ts'),
132
+ prettier.format(web, { ...format, parser: 'typescript' }),
133
+ ],
134
+ ]);
135
+ }
136
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
137
+ const outputs = generateOutputs();
138
+ const stale = [...outputs].filter(
139
+ ([file, text]) =>
140
+ !fs.existsSync(file) || fs.readFileSync(file, 'utf8') !== text
141
+ );
142
+ if (process.argv.includes('--check') && stale.length)
143
+ throw new Error('Stale NativeList icon metadata');
144
+ if (!process.argv.includes('--check'))
145
+ for (const [file, text] of stale) {
146
+ fs.mkdirSync(path.dirname(file), { recursive: true });
147
+ fs.writeFileSync(file, text);
148
+ }
149
+ console.log(JSON.stringify({ outputs: outputs.size, changed: stale.length }));
150
+ }
@@ -0,0 +1,288 @@
1
+ import assert from 'node:assert/strict';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import test from 'node:test';
7
+ import { generateOutputs, resolveRecords } from './generate-icons.mjs';
8
+ const scripts = path.dirname(fileURLToPath(import.meta.url));
9
+ const packageRoot = path.dirname(scripts);
10
+ const fixtureRoot = path.resolve(
11
+ packageRoot,
12
+ '../../example/react-native/icon-fixtures/native/icons'
13
+ );
14
+ const manifest = JSON.parse(
15
+ fs.readFileSync(path.join(scripts, 'native-list-icons.json'), 'utf8')
16
+ );
17
+ const catalogFile = path.join(fixtureRoot, 'manifest.json');
18
+ const catalog = JSON.parse(fs.readFileSync(catalogFile, 'utf8'));
19
+ // Frozen migration coverage, independent from the production manifest.
20
+ const migrationCoverage = {
21
+ ios: [
22
+ 'AccountErrorCustom',
23
+ 'AllNetworksSolid',
24
+ 'AppleBrand',
25
+ 'ArrowBottomOutline',
26
+ 'ArrowTopOutline',
27
+ 'BadgeVerifiedSolid',
28
+ 'BotIllus',
29
+ 'ChartTrendingUpOutline',
30
+ 'CheckboxCheckedCustom',
31
+ 'CheckboxIndeterminateCustom',
32
+ 'ChevronBottomOutline',
33
+ 'ChevronGrabberVerOutline',
34
+ 'ChevronRightSmallOutline',
35
+ 'ChevronTopOutline',
36
+ 'CrossedSmallSolid',
37
+ 'CryptoCoinOutline',
38
+ 'Document2Outline',
39
+ 'DotHorOutline',
40
+ 'DotVerSolid',
41
+ 'DragOutline',
42
+ 'ErrorSolid',
43
+ 'GasSolid',
44
+ 'GlobusOutline',
45
+ 'GoogleIllus',
46
+ 'ImageSquareWavesOutline',
47
+ 'InfoCircleOutline',
48
+ 'LockSolid',
49
+ 'MinusCircleOutline',
50
+ 'MinusCircleSolid',
51
+ 'PencilOutline',
52
+ 'PlusCircleOutline',
53
+ 'PlusLargeOutline',
54
+ 'PlusSmallOutline',
55
+ 'QuestionmarkSolid',
56
+ 'RefreshCcwOutline',
57
+ 'ShieldExclamationOutline',
58
+ 'SpeakerPromoteOutline',
59
+ 'StarOutline',
60
+ 'StarSolid',
61
+ 'SwapHorOutline',
62
+ 'TicketOutline',
63
+ 'WalletOutline',
64
+ ],
65
+ android: [
66
+ 'AccountErrorCustom',
67
+ 'AllNetworksSolid',
68
+ 'AppleBrand',
69
+ 'ArrowBottomOutline',
70
+ 'ArrowTopOutline',
71
+ 'BadgeVerifiedSolid',
72
+ 'BotIllus',
73
+ 'ChartTrendingUpOutline',
74
+ 'ChevronBottomOutline',
75
+ 'ChevronGrabberVerOutline',
76
+ 'ChevronRightSmallOutline',
77
+ 'ChevronTopOutline',
78
+ 'Circle',
79
+ 'CrossedSmallSolid',
80
+ 'CryptoCoinOutline',
81
+ 'Document2Outline',
82
+ 'DotHorOutline',
83
+ 'DotVerSolid',
84
+ 'DragOutline',
85
+ 'ErrorSolid',
86
+ 'GasSolid',
87
+ 'GlobusOutline',
88
+ 'GoogleIllus',
89
+ 'ImageSquareWavesOutline',
90
+ 'InfoCircleOutline',
91
+ 'LockSolid',
92
+ 'MinusCircleOutline',
93
+ 'MinusCircleSolid',
94
+ 'PencilOutline',
95
+ 'PlusCircleOutline',
96
+ 'PlusLargeOutline',
97
+ 'PlusSmallOutline',
98
+ 'QuestionmarkSolid',
99
+ 'RefreshCcwOutline',
100
+ 'ShieldExclamationOutline',
101
+ 'SpeakerPromoteOutline',
102
+ 'StarOutline',
103
+ 'StarSolid',
104
+ 'SwapHorOutline',
105
+ 'TicketOutline',
106
+ 'WalletOutline',
107
+ ],
108
+ web: [
109
+ 'AccountErrorCustom',
110
+ 'AllNetworksSolid',
111
+ 'AppleBrand',
112
+ 'BadgeVerifiedSolid',
113
+ 'BotIllus',
114
+ 'CheckboxCheckedCustom',
115
+ 'CheckboxIndeterminateCustom',
116
+ 'ChevronRightSmallOutline',
117
+ 'Circle',
118
+ 'CrossedSmallSolid',
119
+ 'CryptoCoinOutline',
120
+ 'Document2Outline',
121
+ 'DotHorOutline',
122
+ 'DotVerSolid',
123
+ 'DragOutline',
124
+ 'GasSolid',
125
+ 'GlobusOutline',
126
+ 'GoogleIllus',
127
+ 'LockSolid',
128
+ 'PencilOutline',
129
+ 'PlusLargeOutline',
130
+ 'PlusSmallOutline',
131
+ 'RefreshCcwOutline',
132
+ 'TicketOutline',
133
+ 'WalletOutline',
134
+ ],
135
+ };
136
+ function assertRegistryCoverage(entries) {
137
+ for (const platform of Object.keys(migrationCoverage))
138
+ assert.deepEqual(
139
+ entries
140
+ .filter((entry) => entry.platforms.includes(platform))
141
+ .map((entry) => entry.name)
142
+ .sort(),
143
+ migrationCoverage[platform]
144
+ );
145
+ }
146
+
147
+ test('preserves frozen coverage, canonical aliases and intrinsic metadata without path copies', () => {
148
+ assertRegistryCoverage(manifest);
149
+ assert.throws(() =>
150
+ assertRegistryCoverage(
151
+ manifest.map((e) =>
152
+ e.name === 'DotVerSolid' ? { ...e, name: 'RenamedDotVerSolid' } : e
153
+ )
154
+ )
155
+ );
156
+ const records = resolveRecords(manifest, catalog, fixtureRoot);
157
+ assert.equal(
158
+ records.find((e) => e.name === 'BadgeVerifiedSolid').canonical,
159
+ 'BadgeRecognizedSolid'
160
+ );
161
+ for (const [name, w, h] of [
162
+ ['AppleBrand', 16, 20],
163
+ ['CheckboxCheckedCustom', 16, 16],
164
+ ['AccountErrorCustom', 18, 18],
165
+ ]) {
166
+ const record = records.find((e) => e.name === name);
167
+ assert.deepEqual([record.width, record.height], [w, h]);
168
+ }
169
+ const outputs = generateOutputs(fixtureRoot);
170
+ assert.equal(outputs.size, 3);
171
+ assert.deepEqual([...outputs], [...generateOutputs(fixtureRoot)]);
172
+ const contents = [...outputs.values()];
173
+ assert.ok(
174
+ contents.every(
175
+ (text) => !text.includes(fixtureRoot) && !text.includes('<path')
176
+ )
177
+ );
178
+ const keys = [
179
+ [...contents[0].matchAll(/^ "([^"]+)":/gm)].map((m) => m[1]),
180
+ [...contents[1].matchAll(/^ "([^"]+)" to /gm)].map((m) => m[1]),
181
+ [...contents[2].matchAll(/^ ([A-Za-z0-9]+):/gm)].map((m) => m[1]),
182
+ ];
183
+ assert.deepEqual(keys, [
184
+ migrationCoverage.ios,
185
+ migrationCoverage.android,
186
+ migrationCoverage.web,
187
+ ]);
188
+ });
189
+ test('rejects invalid coverage metadata and missing, ambiguous or stale canonical sources', () => {
190
+ for (const invalid of [
191
+ { ...manifest[0], name: undefined },
192
+ { ...manifest[0], platforms: ['other'] },
193
+ { ...manifest[0], platforms: ['ios', 'ios'] },
194
+ ])
195
+ assert.throws(
196
+ () => resolveRecords([invalid], catalog, fixtureRoot),
197
+ /Invalid icon manifest/
198
+ );
199
+ assert.throws(
200
+ () => resolveRecords([manifest[0], manifest[0]], catalog, fixtureRoot),
201
+ /Invalid icon manifest/
202
+ );
203
+ assert.throws(
204
+ () => resolveRecords(manifest, {}, fixtureRoot),
205
+ /Missing shared SVG/
206
+ );
207
+ assert.throws(
208
+ () =>
209
+ resolveRecords(
210
+ manifest,
211
+ {
212
+ ...catalog,
213
+ Duplicate:
214
+ catalog[
215
+ Object.keys(catalog).find(
216
+ (k) => catalog[k].source === manifest[0].source
217
+ )
218
+ ],
219
+ },
220
+ fixtureRoot
221
+ ),
222
+ /Ambiguous shared SVG/
223
+ );
224
+ const changed = structuredClone(catalog);
225
+ changed[
226
+ Object.keys(changed).find((k) => changed[k].source === manifest[0].source)
227
+ ].digest = '0';
228
+ assert.throws(() => resolveRecords(manifest, changed, fixtureRoot), /stale/);
229
+ });
230
+ test('generation is idempotent, check is read-only and invalid late input cannot partially write', () => {
231
+ const tempRoot = path.join(packageRoot, '.tmp');
232
+ fs.mkdirSync(tempRoot, { recursive: true });
233
+ const temp = fs.mkdtempSync(path.join(tempRoot, 'metadata-'));
234
+ try {
235
+ const sdk = path.join(temp, 'native-views/react-native-native-list');
236
+ fs.mkdirSync(path.join(sdk, 'scripts'), { recursive: true });
237
+ for (const name of ['generate-icons.mjs', 'native-list-icons.json'])
238
+ fs.copyFileSync(
239
+ path.join(scripts, name),
240
+ path.join(sdk, 'scripts', name)
241
+ );
242
+ fs.copyFileSync(
243
+ path.join(packageRoot, 'package.json'),
244
+ path.join(sdk, 'package.json')
245
+ );
246
+ fs.symlinkSync(
247
+ path.resolve(packageRoot, '../../node_modules'),
248
+ path.join(temp, 'node_modules'),
249
+ 'dir'
250
+ );
251
+ const targetCatalog = path.join(
252
+ temp,
253
+ 'example/react-native/icon-fixtures/native/icons/manifest.json'
254
+ );
255
+ fs.mkdirSync(path.dirname(targetCatalog), { recursive: true });
256
+ fs.cpSync(fixtureRoot, path.dirname(targetCatalog), { recursive: true });
257
+ const run = (check = false) =>
258
+ spawnSync(
259
+ process.execPath,
260
+ [
261
+ path.join(sdk, 'scripts/generate-icons.mjs'),
262
+ ...(check ? ['--check'] : []),
263
+ ],
264
+ { encoding: 'utf8' }
265
+ );
266
+ assert.equal(run().status, 0);
267
+ assert.equal(JSON.parse(run().stdout).changed, 0);
268
+ assert.equal(run(true).status, 0);
269
+ const files = [...generateOutputs(fixtureRoot, sdk).keys()];
270
+ const snapshot = () => files.map((file) => fs.readFileSync(file, 'utf8'));
271
+ fs.appendFileSync(files[0], '// stale\n');
272
+ const stale = snapshot();
273
+ assert.notEqual(run(true).status, 0);
274
+ assert.deepEqual(snapshot(), stale);
275
+ assert.equal(run().status, 0);
276
+ const before = snapshot();
277
+ const changed = structuredClone(catalog);
278
+ changed[
279
+ Object.keys(changed).find((k) => changed[k].source === manifest[0].source)
280
+ ].viewBox = [0, 0, 12, 12];
281
+ delete changed.DotVerSolid;
282
+ fs.writeFileSync(targetCatalog, JSON.stringify(changed));
283
+ assert.notEqual(run().status, 0);
284
+ assert.deepEqual(snapshot(), before);
285
+ } finally {
286
+ fs.rmSync(temp, { recursive: true, force: true });
287
+ }
288
+ });
@@ -0,0 +1,260 @@
1
+ [
2
+ {
3
+ "name": "AccountErrorCustom",
4
+ "source": "custom/account-error.svg",
5
+ "platforms": ["ios", "android", "web"],
6
+ "rendering": "original"
7
+ },
8
+ {
9
+ "name": "AllNetworksSolid",
10
+ "source": "solid/all-networks.svg",
11
+ "platforms": ["ios", "android", "web"],
12
+ "rendering": "template"
13
+ },
14
+ {
15
+ "name": "AppleBrand",
16
+ "source": "brand/apple.svg",
17
+ "platforms": ["ios", "android", "web"],
18
+ "rendering": "template"
19
+ },
20
+ {
21
+ "name": "ArrowBottomOutline",
22
+ "source": "outline/arrow-bottom.svg",
23
+ "platforms": ["ios", "android"],
24
+ "rendering": "template"
25
+ },
26
+ {
27
+ "name": "ArrowTopOutline",
28
+ "source": "outline/arrow-top.svg",
29
+ "platforms": ["ios", "android"],
30
+ "rendering": "template"
31
+ },
32
+ {
33
+ "name": "BadgeVerifiedSolid",
34
+ "source": "solid/badge-recognized.svg",
35
+ "platforms": ["ios", "android", "web"],
36
+ "rendering": "template"
37
+ },
38
+ {
39
+ "name": "BotIllus",
40
+ "source": "illus/bot.svg",
41
+ "platforms": ["ios", "android", "web"],
42
+ "rendering": "original"
43
+ },
44
+ {
45
+ "name": "ChartTrendingUpOutline",
46
+ "source": "outline/chart-trending-up.svg",
47
+ "platforms": ["ios", "android"],
48
+ "rendering": "template"
49
+ },
50
+ {
51
+ "name": "CheckboxCheckedCustom",
52
+ "source": "custom/checkbox-checked.svg",
53
+ "platforms": ["ios", "web"],
54
+ "rendering": "template"
55
+ },
56
+ {
57
+ "name": "CheckboxIndeterminateCustom",
58
+ "source": "custom/checkbox-indeterminate.svg",
59
+ "platforms": ["ios", "web"],
60
+ "rendering": "template"
61
+ },
62
+ {
63
+ "name": "ChevronBottomOutline",
64
+ "source": "outline/chevron-bottom.svg",
65
+ "platforms": ["ios", "android"],
66
+ "rendering": "template"
67
+ },
68
+ {
69
+ "name": "ChevronGrabberVerOutline",
70
+ "source": "outline/chevron-grabber-ver.svg",
71
+ "platforms": ["ios", "android"],
72
+ "rendering": "template"
73
+ },
74
+ {
75
+ "name": "ChevronRightSmallOutline",
76
+ "source": "outline/chevron-right-small.svg",
77
+ "platforms": ["ios", "android", "web"],
78
+ "rendering": "template"
79
+ },
80
+ {
81
+ "name": "ChevronTopOutline",
82
+ "source": "outline/chevron-top.svg",
83
+ "platforms": ["ios", "android"],
84
+ "rendering": "template"
85
+ },
86
+ {
87
+ "name": "Circle",
88
+ "source": null,
89
+ "platforms": ["android", "web"],
90
+ "rendering": "template"
91
+ },
92
+ {
93
+ "name": "CrossedSmallSolid",
94
+ "source": "solid/crossed-small.svg",
95
+ "platforms": ["ios", "android", "web"],
96
+ "rendering": "template"
97
+ },
98
+ {
99
+ "name": "CryptoCoinOutline",
100
+ "source": "outline/crypto-coin.svg",
101
+ "platforms": ["ios", "android", "web"],
102
+ "rendering": "template"
103
+ },
104
+ {
105
+ "name": "Document2Outline",
106
+ "source": "outline/document-2.svg",
107
+ "platforms": ["ios", "android", "web"],
108
+ "rendering": "template"
109
+ },
110
+ {
111
+ "name": "DotHorOutline",
112
+ "source": "outline/dot-hor.svg",
113
+ "platforms": ["ios", "android", "web"],
114
+ "rendering": "template"
115
+ },
116
+ {
117
+ "name": "DotVerSolid",
118
+ "source": "solid/dot-ver.svg",
119
+ "platforms": ["ios", "android", "web"],
120
+ "rendering": "template"
121
+ },
122
+ {
123
+ "name": "DragOutline",
124
+ "source": "outline/drag.svg",
125
+ "platforms": ["ios", "android", "web"],
126
+ "rendering": "template"
127
+ },
128
+ {
129
+ "name": "ErrorSolid",
130
+ "source": "solid/error.svg",
131
+ "platforms": ["ios", "android"],
132
+ "rendering": "template"
133
+ },
134
+ {
135
+ "name": "GasSolid",
136
+ "source": "solid/gas.svg",
137
+ "platforms": ["ios", "android", "web"],
138
+ "rendering": "template"
139
+ },
140
+ {
141
+ "name": "GlobusOutline",
142
+ "source": "outline/globus.svg",
143
+ "platforms": ["ios", "android", "web"],
144
+ "rendering": "template"
145
+ },
146
+ {
147
+ "name": "GoogleIllus",
148
+ "source": "illus/google.svg",
149
+ "platforms": ["ios", "android", "web"],
150
+ "rendering": "original"
151
+ },
152
+ {
153
+ "name": "ImageSquareWavesOutline",
154
+ "source": "outline/image-square-waves.svg",
155
+ "platforms": ["ios", "android"],
156
+ "rendering": "template"
157
+ },
158
+ {
159
+ "name": "InfoCircleOutline",
160
+ "source": "outline/info-circle.svg",
161
+ "platforms": ["ios", "android"],
162
+ "rendering": "template"
163
+ },
164
+ {
165
+ "name": "LockSolid",
166
+ "source": "solid/lock.svg",
167
+ "platforms": ["ios", "android", "web"],
168
+ "rendering": "template"
169
+ },
170
+ {
171
+ "name": "MinusCircleOutline",
172
+ "source": "outline/minus-circle.svg",
173
+ "platforms": ["ios", "android"],
174
+ "rendering": "template"
175
+ },
176
+ {
177
+ "name": "MinusCircleSolid",
178
+ "source": "solid/minus-circle.svg",
179
+ "platforms": ["ios", "android"],
180
+ "rendering": "template"
181
+ },
182
+ {
183
+ "name": "PencilOutline",
184
+ "source": "outline/pencil.svg",
185
+ "platforms": ["ios", "android", "web"],
186
+ "rendering": "template"
187
+ },
188
+ {
189
+ "name": "PlusCircleOutline",
190
+ "source": "outline/plus-circle.svg",
191
+ "platforms": ["ios", "android"],
192
+ "rendering": "template"
193
+ },
194
+ {
195
+ "name": "PlusLargeOutline",
196
+ "source": "outline/plus-large.svg",
197
+ "platforms": ["ios", "android", "web"],
198
+ "rendering": "template"
199
+ },
200
+ {
201
+ "name": "PlusSmallOutline",
202
+ "source": "outline/plus-small.svg",
203
+ "platforms": ["ios", "android", "web"],
204
+ "rendering": "template"
205
+ },
206
+ {
207
+ "name": "QuestionmarkSolid",
208
+ "source": "solid/questionmark.svg",
209
+ "platforms": ["ios", "android"],
210
+ "rendering": "template"
211
+ },
212
+ {
213
+ "name": "RefreshCcwOutline",
214
+ "source": "outline/refresh-ccw.svg",
215
+ "platforms": ["ios", "android", "web"],
216
+ "rendering": "template"
217
+ },
218
+ {
219
+ "name": "ShieldExclamationOutline",
220
+ "source": "outline/shield-exclamation.svg",
221
+ "platforms": ["ios", "android"],
222
+ "rendering": "template"
223
+ },
224
+ {
225
+ "name": "SpeakerPromoteOutline",
226
+ "source": "outline/speaker-promote.svg",
227
+ "platforms": ["ios", "android"],
228
+ "rendering": "template"
229
+ },
230
+ {
231
+ "name": "StarOutline",
232
+ "source": "outline/star.svg",
233
+ "platforms": ["ios", "android"],
234
+ "rendering": "template"
235
+ },
236
+ {
237
+ "name": "StarSolid",
238
+ "source": "solid/star.svg",
239
+ "platforms": ["ios", "android"],
240
+ "rendering": "template"
241
+ },
242
+ {
243
+ "name": "SwapHorOutline",
244
+ "source": "outline/swap-hor.svg",
245
+ "platforms": ["ios", "android"],
246
+ "rendering": "template"
247
+ },
248
+ {
249
+ "name": "TicketOutline",
250
+ "source": "outline/ticket.svg",
251
+ "platforms": ["ios", "android", "web"],
252
+ "rendering": "template"
253
+ },
254
+ {
255
+ "name": "WalletOutline",
256
+ "source": "outline/wallet.svg",
257
+ "platforms": ["ios", "android", "web"],
258
+ "rendering": "template"
259
+ }
260
+ ]