tf-checkout-react-native 2.0.0-beta.7 → 2.0.0-beta.8
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tf-checkout-react-native",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.8",
|
|
4
4
|
"description": "TheTicketFairy ReactNative Checkout library",
|
|
5
5
|
"homepage": "https://github.com/theticketfairy/tf-shared-libraries#readme",
|
|
6
6
|
"author": "The Ticket Fairy",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"!**/.*"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
|
-
"test": "jest --passWithNoTests",
|
|
40
|
+
"test": "node scripts/verify-discussion-support.cjs && jest --passWithNoTests",
|
|
41
41
|
"typecheck": "tsc -p tsconfig.build.json",
|
|
42
42
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
43
43
|
"lint:fix": "eslint \"**/*.{js,ts,tsx}\" --fix",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/* Executes the real native support Pressable; no native OS/mail account required. */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports -- Node CommonJS verifier, matching the package's script entry. */
|
|
3
|
+
const assert = require('node:assert/strict')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const vm = require('node:vm')
|
|
7
|
+
const ts = require('typescript')
|
|
8
|
+
const React = require('react')
|
|
9
|
+
const opened = []
|
|
10
|
+
let rejectOpen = false
|
|
11
|
+
const source = fs.readFileSync(
|
|
12
|
+
path.join(__dirname, '../src/sections/EventDiscussionView.tsx'),
|
|
13
|
+
'utf8'
|
|
14
|
+
)
|
|
15
|
+
const compiled = ts.transpileModule(source, {
|
|
16
|
+
compilerOptions: {
|
|
17
|
+
module: ts.ModuleKind.CommonJS,
|
|
18
|
+
target: ts.ScriptTarget.ES2022,
|
|
19
|
+
jsx: ts.JsxEmit.React,
|
|
20
|
+
esModuleInterop: true,
|
|
21
|
+
},
|
|
22
|
+
}).outputText
|
|
23
|
+
const moduleExports = {}
|
|
24
|
+
vm.runInNewContext(compiled, {
|
|
25
|
+
exports: moduleExports,
|
|
26
|
+
require: (name) => {
|
|
27
|
+
if (name === 'react')
|
|
28
|
+
return {
|
|
29
|
+
...React,
|
|
30
|
+
useRef: (value) => ({ current: value }),
|
|
31
|
+
useState: (value) => [value, () => {}],
|
|
32
|
+
}
|
|
33
|
+
if (name === 'react-native')
|
|
34
|
+
return {
|
|
35
|
+
...Object.fromEntries(
|
|
36
|
+
[
|
|
37
|
+
'ActivityIndicator',
|
|
38
|
+
'FlatList',
|
|
39
|
+
'Image',
|
|
40
|
+
'KeyboardAvoidingView',
|
|
41
|
+
'Modal',
|
|
42
|
+
'Pressable',
|
|
43
|
+
'Text',
|
|
44
|
+
'TextInput',
|
|
45
|
+
'View',
|
|
46
|
+
].map((name) => [name, name])
|
|
47
|
+
),
|
|
48
|
+
Platform: { OS: 'ios' },
|
|
49
|
+
StyleSheet: { create: (styles) => styles },
|
|
50
|
+
Linking: {
|
|
51
|
+
openURL: (url) => {
|
|
52
|
+
opened.push(url)
|
|
53
|
+
return rejectOpen
|
|
54
|
+
? Promise.reject(Error('No mail client'))
|
|
55
|
+
: Promise.resolve()
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
if (name === 'tf-checkout-shared') return { EVENT_COMMENT_IMAGE_MAX_COUNT: 5 }
|
|
60
|
+
if (name === '../ui/CheckoutProvider') return { useTheme: () => ({}) }
|
|
61
|
+
if (name === '../ui/useAccountT') return { useAccountT: () => (value) => value }
|
|
62
|
+
if (name === './discussion/CommentImages')
|
|
63
|
+
return { CommentImages: 'CommentImages', CommentImageDrafts: 'CommentImageDrafts' }
|
|
64
|
+
if (name === './discussion/SwipeReply') return { SwipeReply: 'SwipeReply' }
|
|
65
|
+
throw Error('Unexpected dependency: ' + name)
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
const links = (node) =>
|
|
69
|
+
!node || typeof node !== 'object'
|
|
70
|
+
? []
|
|
71
|
+
: [
|
|
72
|
+
...(node.type === 'Pressable' && node.props.accessibilityRole === 'link'
|
|
73
|
+
? [node]
|
|
74
|
+
: []),
|
|
75
|
+
...React.Children.toArray(node.props?.children).flatMap(links),
|
|
76
|
+
]
|
|
77
|
+
const render = (url) => {
|
|
78
|
+
const element = moduleExports.EventDiscussionView({
|
|
79
|
+
eventId: '42',
|
|
80
|
+
discussion: {
|
|
81
|
+
scopeKey: 'synthetic-actor-7',
|
|
82
|
+
comments: [],
|
|
83
|
+
blockedAuthors: [],
|
|
84
|
+
imagesBase64: [],
|
|
85
|
+
body: '',
|
|
86
|
+
canPost: false,
|
|
87
|
+
supportUrl: url,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
const tree = element.type(element.props)
|
|
91
|
+
const support = links(tree)
|
|
92
|
+
assert.equal(support.length, 1)
|
|
93
|
+
return support[0]
|
|
94
|
+
}
|
|
95
|
+
;(async () => {
|
|
96
|
+
// This exact URI comes from EventDiscussionService::page, including its subject.
|
|
97
|
+
const canonical = 'mailto:support@theticketfairy.com?subject=Event%20comment%20report'
|
|
98
|
+
for (const url of [canonical, 'https://www.ticketfairy.com/support']) {
|
|
99
|
+
opened.length = 0
|
|
100
|
+
const support = render(url)
|
|
101
|
+
assert.deepEqual(opened, [], 'rendering must never open or send anything')
|
|
102
|
+
support.props.onPress()
|
|
103
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
104
|
+
assert.deepEqual(opened, [url], 'the user press must reach the native Linking port')
|
|
105
|
+
}
|
|
106
|
+
for (const url of [
|
|
107
|
+
'javascript:alert(1)',
|
|
108
|
+
'file:///private',
|
|
109
|
+
'http://example.invalid',
|
|
110
|
+
'tel:123',
|
|
111
|
+
]) {
|
|
112
|
+
opened.length = 0
|
|
113
|
+
render(url).props.onPress()
|
|
114
|
+
assert.deepEqual(opened, [], 'unsupported schemes must not reach the OS')
|
|
115
|
+
}
|
|
116
|
+
rejectOpen = true
|
|
117
|
+
opened.length = 0
|
|
118
|
+
render(canonical).props.onPress()
|
|
119
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
120
|
+
assert.deepEqual(
|
|
121
|
+
opened,
|
|
122
|
+
[canonical],
|
|
123
|
+
'an unavailable mail client must be handled quietly'
|
|
124
|
+
)
|
|
125
|
+
process.stdout.write(
|
|
126
|
+
'Native discussion support PASS: user press, canonical mailto, HTTPS, unsafe schemes and unavailable client.\n'
|
|
127
|
+
)
|
|
128
|
+
})().catch((error) => {
|
|
129
|
+
console.error(error)
|
|
130
|
+
process.exitCode = 1
|
|
131
|
+
})
|
|
@@ -423,7 +423,7 @@ const EventDiscussionBody = ({
|
|
|
423
423
|
accessibilityRole="link"
|
|
424
424
|
style={s.support}
|
|
425
425
|
onPress={() => {
|
|
426
|
-
if (/^https
|
|
426
|
+
if (/^(https:\/\/|mailto:)/i.test(d.supportUrl))
|
|
427
427
|
void Linking.openURL(d.supportUrl).catch(() => undefined)
|
|
428
428
|
}}
|
|
429
429
|
>
|