@hellotext/hellotext 1.4.1 → 1.5.1
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/.babelrc +6 -3
- package/.github/workflows/ci.yml +1 -1
- package/.prettierignore +2 -0
- package/README.md +30 -170
- package/__tests__/api/sessions_test.js +12 -0
- package/__tests__/builders/input_builder_test.js +117 -0
- package/__tests__/{event_emitter_test.js → core/event_test.js} +26 -6
- package/__tests__/hellotext_test.js +12 -0
- package/__tests__/models/business_test.js +26 -0
- package/__tests__/models/cookies_test.js +25 -0
- package/__tests__/models/form_collection_test.js +100 -0
- package/__tests__/models/form_test.js +29 -0
- package/__tests__/models/query_test.js +97 -0
- package/dist/hellotext.js +1 -1
- package/docs/forms.md +127 -0
- package/docs/tracking.md +155 -0
- package/lib/api/businesses.js +45 -0
- package/lib/api/events.js +54 -0
- package/lib/api/forms.js +65 -0
- package/lib/api/index.js +51 -0
- package/lib/api/response.js +57 -0
- package/lib/api/sessions.js +46 -0
- package/lib/api/submissions.js +61 -0
- package/lib/api.js +73 -0
- package/lib/builders/inputBuilder.js +97 -0
- package/lib/builders/input_builder.js +61 -0
- package/lib/builders/logo_builder.js +38 -0
- package/lib/builders/otp_builder.js +39 -0
- package/lib/controllers/form_controller.js +139 -0
- package/lib/controllers/otp_controller.js +114 -0
- package/lib/core/configuration.js +37 -0
- package/lib/core/event.js +73 -0
- package/lib/core/index.js +20 -0
- package/lib/errors/index.js +19 -0
- package/lib/errors/invalid_event.js +34 -0
- package/lib/errors/not_initialized_error.js +34 -0
- package/lib/event.js +159 -32
- package/lib/eventEmitter.js +139 -46
- package/lib/forms.js +133 -0
- package/lib/hellotext.js +53 -105
- package/lib/index.js +7 -0
- package/lib/locales/en.js +20 -0
- package/lib/locales/es.js +20 -0
- package/lib/locales/index.js +14 -0
- package/lib/models/business.js +57 -0
- package/lib/models/configuration.js +66 -0
- package/lib/models/cookies.js +34 -0
- package/lib/models/form.js +148 -0
- package/lib/models/form_collection.js +96 -0
- package/lib/models/index.js +40 -0
- package/lib/models/query.js +51 -0
- package/lib/models/step.js +45 -0
- package/lib/query.js +65 -32
- package/lib/response.js +82 -42
- package/package.json +6 -3
- package/src/api/businesses.js +18 -0
- package/src/api/events.js +24 -0
- package/src/api/forms.js +30 -0
- package/src/api/index.js +24 -0
- package/src/{response.js → api/response.js} +9 -4
- package/src/api/sessions.js +23 -0
- package/src/api/submissions.js +30 -0
- package/src/builders/input_builder.js +55 -0
- package/src/builders/logo_builder.js +25 -0
- package/src/builders/otp_builder.js +42 -0
- package/src/controllers/form_controller.js +105 -0
- package/src/controllers/otp_controller.js +77 -0
- package/src/core/configuration.js +16 -0
- package/src/core/event.js +54 -0
- package/src/core/index.js +2 -0
- package/src/errors/index.js +2 -0
- package/src/hellotext.js +50 -72
- package/src/index.js +10 -0
- package/src/locales/en.js +13 -0
- package/src/locales/es.js +13 -0
- package/src/locales/index.js +7 -0
- package/src/models/business.js +41 -0
- package/src/models/cookies.js +16 -0
- package/src/models/form.js +133 -0
- package/src/models/form_collection.js +81 -0
- package/src/models/index.js +5 -0
- package/src/models/query.js +33 -0
- package/styles/index.css +32 -0
- package/webpack.config.js +15 -9
- package/__tests__/event_test.js +0 -21
- package/__tests__/query_test.js +0 -13
- package/src/event.js +0 -15
- package/src/eventEmitter.js +0 -28
- package/src/query.js +0 -21
- /package/src/errors/{invalidEvent.js → invalid_event.js} +0 -0
- /package/src/errors/{notInitializedError.js → not_initialized_error.js} +0 -0
package/.babelrc
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
]
|
|
11
11
|
],
|
|
12
12
|
"plugins": [
|
|
13
|
-
[
|
|
14
|
-
"
|
|
15
|
-
|
|
13
|
+
[
|
|
14
|
+
"@babel/plugin-proposal-private-methods",
|
|
15
|
+
{
|
|
16
|
+
"loose": true
|
|
17
|
+
}
|
|
18
|
+
],
|
|
16
19
|
["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
|
|
17
20
|
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
|
18
21
|
"@babel/plugin-transform-classes"
|
package/.github/workflows/ci.yml
CHANGED
package/.prettierignore
ADDED
package/README.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Hellotext.js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official [Hellotext](https://www.hellotext.com) (client-side) JavaScript library.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This library allows you the following,
|
|
6
|
+
|
|
7
|
+
- Track events happening on your site to [Hellotext](https://www.hellotext.com) in real-time.
|
|
8
|
+
- Use Hellotext Forms to dynamically collect data from your customers based on your specific business requirements.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
6
12
|
|
|
7
13
|
### Using NPM
|
|
8
14
|
|
|
@@ -21,7 +27,7 @@ yarn add @hellotext/hellotext
|
|
|
21
27
|
Import the library into your app.
|
|
22
28
|
|
|
23
29
|
```javascript
|
|
24
|
-
import Hellotext from
|
|
30
|
+
import Hellotext from '@hellotext/hellotext'
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
Initialize the library passing the public `HELLOTEXT_BUSINESS_ID` identifier that represents the business.
|
|
@@ -29,165 +35,17 @@ Initialize the library passing the public `HELLOTEXT_BUSINESS_ID` identifier tha
|
|
|
29
35
|
You can find it from the business's settings page.
|
|
30
36
|
|
|
31
37
|
```javascript
|
|
32
|
-
Hellotext.initialize(
|
|
38
|
+
Hellotext.initialize('HELLOTEXT_BUSINESS_ID')
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
Failing to initialize the class before calling any other method will throw a `NotInitializedError`.
|
|
36
42
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
Tracking events is straightforward and perhaps the simplest example is tracking a page view:
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
Hellotext.track("page.viewed");
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
In the example above only the name of the action is required.
|
|
46
|
-
|
|
47
|
-
### Handling Responses
|
|
48
|
-
|
|
49
|
-
The `track` method returns a Promise that can be `await`ed using the async/await syntax. Or using `.then` on the returned Promise.
|
|
50
|
-
|
|
51
|
-
```javascript
|
|
52
|
-
const response = await Hellotext.track("page.viewed");
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
The return of the `Hellotext.track` method is an instance of a `Response` object that ships with the package. You can check the status of the response via methods, like:
|
|
56
|
-
|
|
57
|
-
```javascript
|
|
58
|
-
if(response.failed) {
|
|
59
|
-
console.log("failed because", response.data)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if(response.succeeded) {
|
|
63
|
-
console.log("success")
|
|
64
|
-
console.log(response.data) // { status: "received" }
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Parameters
|
|
69
|
-
|
|
70
|
-
The parameters passed to the action must be a valid set of parameters as described in
|
|
71
|
-
[Tracking Actions](https://www.hellotext.com/api#tracking).
|
|
72
|
-
|
|
73
|
-
#### URL Parameter
|
|
74
|
-
|
|
75
|
-
The library takes care of handling the `url` parameter with the current URL automatically and is not required to specify it explicitly.
|
|
76
|
-
If you want to provide another url, you can pass a `url` key in the params object when tracking an event.
|
|
77
|
-
|
|
78
|
-
```javascript
|
|
79
|
-
Hellotext.track("page.viewed", {
|
|
80
|
-
url: "www.example.org"
|
|
81
|
-
});
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Errors
|
|
85
|
-
|
|
86
|
-
Failing to provide valid set of parameters will result in an error object being returned, describing the parameters that did not satisfy the rules.
|
|
87
|
-
|
|
88
|
-
```javascript
|
|
89
|
-
const response = await Hellotext.track("app.installed", { app_parameters: { name: "My App" }})
|
|
90
|
-
|
|
91
|
-
console.log(response.data)
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
yields
|
|
95
|
-
|
|
96
|
-
```javascript
|
|
97
|
-
{
|
|
98
|
-
errors: [
|
|
99
|
-
{
|
|
100
|
-
type: 'parameter_not_unique',
|
|
101
|
-
parameter: 'name',
|
|
102
|
-
description: 'The value must be unique and it is already present in another object of the same type.'
|
|
103
|
-
},
|
|
104
|
-
]
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
For a complete list of errors types. See [Error Types](https://www.hellotext.com/api#errors)
|
|
109
|
-
|
|
110
|
-
### Associated objects
|
|
111
|
-
|
|
112
|
-
Generally, most actions also require an associated object. These can be of type [`app`](https://www.hellotext.com/api#apps), [`coupon`](https://www.hellotext.com/api#coupons), [`form`](https://www.hellotext.com/api#forms), [`order`](https://www.hellotext.com/api#orders), [`product`](https://www.hellotext.com/api#products) and [`refund`](https://www.hellotext.com/api#refunds).
|
|
113
|
-
Aside from [Custom Actions](https://www.hellotext.com/api#create_an_action), which don't require the trackable to be present.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
You can create the associated object directly by defining its parameters in a hash:
|
|
117
|
-
|
|
118
|
-
```javascript
|
|
119
|
-
Hellotext.track("order.placed", {
|
|
120
|
-
amount: 395.00,
|
|
121
|
-
currency: "USD",
|
|
122
|
-
order_parameters: {
|
|
123
|
-
"amount": "395.00",
|
|
124
|
-
"reference": "654321",
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
If you want to reuse existing objects, you must pass the identifier of an existing associated object. For example, to track a product purchase the identifier of a previously created product object as the `product`.
|
|
130
|
-
For more information about identifiers, view the [Tracking API](https://www.hellotext.com/api#tracking)
|
|
131
|
-
|
|
132
|
-
```javascript
|
|
133
|
-
Hellotext.track("product.purchased", {
|
|
134
|
-
amount: 395.00,
|
|
135
|
-
currency: "USD",
|
|
136
|
-
product: "erA2RAXE"
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## List of actions
|
|
141
|
-
|
|
142
|
-
The following is a complete list of built-in actions and their required associated objects.
|
|
143
|
-
|
|
144
|
-
| Action | Description | Required Parameter |
|
|
145
|
-
|-----------------------|----------------------------------------------------------| --- |
|
|
146
|
-
| **app.installed** | An app was installed. | `app` or [app_parameters](https://www.hellotext.com/api#app)
|
|
147
|
-
| **app.removed** | An app was removed. | `app` or [app_parameters](https://www.hellotext.com/api#app)
|
|
148
|
-
| **app.spent** | A customer spent on an app. | `app` or [app_parameters](https://www.hellotext.com/api#app)
|
|
149
|
-
| **cart.abandoned** | A cart was abandoned. | `product` or [product_parameters](https://www.hellotext.com/api#products)
|
|
150
|
-
| **cart.added** | Added an item to the cart. | `product` or [product_parameters](https://www.hellotext.com/api#products)
|
|
151
|
-
| **cart.removed** | Removed an item from the cart. | `product` or [product_parameters](https://www.hellotext.com/api#products)
|
|
152
|
-
| **coupon.redeemed** | A coupon was redeem by a customer. | `coupon` or [coupon_parameters](https://www.hellotext.com/api#coupons)
|
|
153
|
-
| **form.completed** | A form was completed by the customer. | `form` or [form_parameters](https://www.hellotext.com/api#forms)
|
|
154
|
-
| **order.placed** | Order has been placed. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
|
|
155
|
-
| **order.confirmed** | Order has been confirmed by you. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
|
|
156
|
-
| **order.cancelled** | Order has been cancelled either by you or your customer. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
|
|
157
|
-
| **order.shipped** | Order has been shipped to your customer. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
|
|
158
|
-
| **order.delivered** | Order has been delivered to your customer. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
|
|
159
|
-
| **page.viewed** | A page was viewed by a customer. | `url`
|
|
160
|
-
| **product.purchased** | A product has been purchased. | `product` or [product_parameters](https://www.hellotext.com/api#products)
|
|
161
|
-
| **product.viewed** | A product page has been viewed. | `product` or [product_parameters](https://www.hellotext.com/api#products)
|
|
162
|
-
| **refund.requested** | A customer requested a refund. | `refund` or [refund_parameters](https://www.hellotext.com/api#refunds)
|
|
163
|
-
| **refund.received** | A refund was issued by you to your customer. | `refund` or [refund_parameters](https://www.hellotext.com/api#refunds)
|
|
164
|
-
|
|
165
|
-
You can also create your **[own defined actions](https://www.hellotext.com/api#actions)**.
|
|
166
|
-
|
|
167
|
-
## Additional Properties
|
|
168
|
-
|
|
169
|
-
You can include additional attributes to the tracked event, additional properties must be included inside the `metadata` object:
|
|
170
|
-
|
|
171
|
-
```javascript
|
|
172
|
-
Hellotext.track("product.purchased", {
|
|
173
|
-
amount: 0.20,
|
|
174
|
-
currency: "USD",
|
|
175
|
-
metadata: {
|
|
176
|
-
myProperty: "custom"
|
|
177
|
-
},
|
|
178
|
-
tracked_at: 1665684173
|
|
179
|
-
});
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### List of additional attributes
|
|
43
|
+
## Documentation
|
|
183
44
|
|
|
184
|
-
|
|
185
|
-
| --- | --- | --- | --- |
|
|
186
|
-
| **amount** | Monetary amount that represents the revenue associated to this tracked event. | float | `0`
|
|
187
|
-
| **currency** | Currency for the `amount` given in ISO 4217 format. | currency | `USD`
|
|
188
|
-
| **metadata** | Set of key-value pairs that you can attach to an event. This can be useful for storing additional information about the object in a structured format. | hash | `{}`
|
|
189
|
-
| **tracked_at** | Original date when the event happened. This is useful if you want to record an event that happened in the past. If no value is provided its value will be the same from `created_at`. | epoch | `null`
|
|
45
|
+
Learn how to leverage the library to track events and collect forms.
|
|
190
46
|
|
|
47
|
+
- [Tracking Events](/docs/tracking.md)
|
|
48
|
+
- [Forms](/docs/forms.md)
|
|
191
49
|
|
|
192
50
|
## Events
|
|
193
51
|
|
|
@@ -209,54 +67,56 @@ Hellotext.removeEventListener(eventName, callback)
|
|
|
209
67
|
### List of events
|
|
210
68
|
|
|
211
69
|
- `session-set`: This event is fired when the session value for `Hellotext.session` is set. Either through an API request, or if the session was found in the cookie.
|
|
70
|
+
- `forms:collected` This event is fired when forms are collected. The callback will receive the array of forms collected.
|
|
71
|
+
- `form:completed` This event is fired when a form has been completed. A form is completed when the user fills all required inputs and verifies their OTP(One-Time Password). The callback will receive the form object that was completed, alongside the data the user filled in the form.
|
|
212
72
|
|
|
213
73
|
## Understanding Sessions
|
|
214
74
|
|
|
215
|
-
The library looks for a session identifier present on the `hellotext_session` query parameter. If the session is not present as a cookie neither it will create a new random session identifier, you can disable this default behaviour via the configuration, see [Configuration Options](#configuration-options) for more information.
|
|
216
|
-
The session is automatically sent to Hellotext any time the `Hellotext.track` method is called.
|
|
75
|
+
The library looks for a session identifier present on the `hellotext_session` query parameter. If the session is not present as a cookie neither it will create a new random session identifier, you can disable this default behaviour via the configuration, see [Configuration Options](#configuration-options) for more information.
|
|
76
|
+
The session is automatically sent to Hellotext any time the `Hellotext.track` method is called.
|
|
217
77
|
|
|
218
78
|
Short links redirections attaches a session identifier to the destination url as `hellotext_session` query parameter. This will identify all the events back to the customer who opened the link.
|
|
219
79
|
|
|
220
80
|
### Get session
|
|
221
81
|
|
|
222
|
-
It is possible to obtain the current session by simply calling `Hellotext.session`.
|
|
82
|
+
It is possible to obtain the current session by simply calling `Hellotext.session`.
|
|
223
83
|
|
|
224
84
|
```javascript
|
|
225
85
|
await Hellotext.session
|
|
226
86
|
// Returns bBJn9vR15yPaYkWmR2QK0jopMeNxrA6l
|
|
227
87
|
```
|
|
228
88
|
|
|
229
|
-
If the session has not been set yet, the result returned will be `undefined`.
|
|
89
|
+
If the session has not been set yet, the result returned will be `undefined`.
|
|
230
90
|
You can check whether the session has been set or not by calling `Hellotext.isInitialized`.
|
|
231
91
|
|
|
232
92
|
```javascript
|
|
233
|
-
if(Hellotext.isInitialized) {
|
|
234
|
-
console.log(
|
|
93
|
+
if (Hellotext.isInitialized) {
|
|
94
|
+
console.log('session is present')
|
|
235
95
|
} else {
|
|
236
|
-
console.log(
|
|
96
|
+
console.log('session has not been set')
|
|
237
97
|
}
|
|
238
98
|
```
|
|
239
99
|
|
|
240
100
|
Moreover, you can hook in and listen for the session being set, such that when it's set, you're notified about the change, like so
|
|
241
101
|
|
|
242
102
|
```javascript
|
|
243
|
-
Hellotext.on(
|
|
244
|
-
console.log(
|
|
103
|
+
Hellotext.on('session-set', session => {
|
|
104
|
+
console.log('session is: ', session)
|
|
245
105
|
})
|
|
246
106
|
```
|
|
247
107
|
|
|
248
108
|
You may want to store the session on your backend when customers are unidentified so you can later [attach it to a profile](https://www.hellotext.com/api#attach_session) when it becomes known.
|
|
249
109
|
|
|
250
|
-
### Configuration
|
|
110
|
+
### Configuration
|
|
251
111
|
|
|
252
|
-
When initializing the library, you may pass an optional configuration object as the second argument.
|
|
112
|
+
When initializing the library, you may pass an optional configuration object as the second argument.
|
|
253
113
|
|
|
254
114
|
```javascript
|
|
255
|
-
Hellotext.initialize(
|
|
115
|
+
Hellotext.initialize('HELLOTEXT_BUSINESS_ID', configurationOptions)
|
|
256
116
|
```
|
|
257
117
|
|
|
258
118
|
#### Configuration Options
|
|
259
119
|
|
|
260
120
|
| Property | Description | Type | Default |
|
|
261
|
-
|
|
262
|
-
| autogenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true
|
|
121
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ------- |
|
|
122
|
+
| autogenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import API from '../../src/api/sessions'
|
|
2
|
+
|
|
3
|
+
beforeEach(() => {
|
|
4
|
+
global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({ id: 1 }) })
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
describe('create', () => {
|
|
8
|
+
it('creates a session object', async () => {
|
|
9
|
+
const response = await new API('M01az53K').create()
|
|
10
|
+
expect(response.id).toEqual(1)
|
|
11
|
+
})
|
|
12
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Hellotext from '../../src/hellotext'
|
|
6
|
+
import { InputBuilder } from '../../src/builders/input_builder'
|
|
7
|
+
|
|
8
|
+
describe('when the input is first_name', () => {
|
|
9
|
+
const data = {
|
|
10
|
+
label: 'First Name',
|
|
11
|
+
type: 'text',
|
|
12
|
+
required: true,
|
|
13
|
+
placeholder: 'Enter your first name',
|
|
14
|
+
kind: 'first_name',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
it('builds an article element that contains a label and input elements', () => {
|
|
18
|
+
const article = InputBuilder.build(data)
|
|
19
|
+
const label = article.querySelector('label')
|
|
20
|
+
const input = article.querySelector('input')
|
|
21
|
+
|
|
22
|
+
expect(label.innerText).toEqual('First Name')
|
|
23
|
+
expect(label.getAttribute('for')).toEqual('first_name')
|
|
24
|
+
|
|
25
|
+
expect(input.id).toEqual('first_name')
|
|
26
|
+
expect(input.type).toEqual('text')
|
|
27
|
+
expect(input.required).toEqual(true)
|
|
28
|
+
expect(input.placeholder).toEqual('Enter your first name')
|
|
29
|
+
expect(input.name).toEqual('first_name')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('when the input is last_name', () => {
|
|
34
|
+
const data = {
|
|
35
|
+
label: 'Last Name',
|
|
36
|
+
type: 'text',
|
|
37
|
+
required: false,
|
|
38
|
+
placeholder: 'Enter your last name',
|
|
39
|
+
kind: 'last_name',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
it('builds an article element that contains a label and input elements', () => {
|
|
43
|
+
const article = InputBuilder.build(data)
|
|
44
|
+
const label = article.querySelector('label')
|
|
45
|
+
const input = article.querySelector('input')
|
|
46
|
+
|
|
47
|
+
expect(label.innerText).toEqual('Last Name')
|
|
48
|
+
expect(label.getAttribute('for')).toEqual('last_name')
|
|
49
|
+
|
|
50
|
+
expect(input.id).toEqual('last_name')
|
|
51
|
+
expect(input.type).toEqual('text')
|
|
52
|
+
expect(input.required).toEqual(false)
|
|
53
|
+
expect(input.placeholder).toEqual('Enter your last name')
|
|
54
|
+
expect(input.name).toEqual('last_name')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('when the input belongs to a property', () => {
|
|
59
|
+
const data = {
|
|
60
|
+
label: 'Email',
|
|
61
|
+
type: 'email',
|
|
62
|
+
required: true,
|
|
63
|
+
placeholder: 'Enter your email',
|
|
64
|
+
kind: 'email',
|
|
65
|
+
property: 'xybz',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
it('builds an article element that contains a label and input elements', () => {
|
|
69
|
+
const article = InputBuilder.build(data)
|
|
70
|
+
const label = article.querySelector('label')
|
|
71
|
+
const input = article.querySelector('input')
|
|
72
|
+
|
|
73
|
+
expect(label.innerText).toEqual('Email')
|
|
74
|
+
expect(label.getAttribute('for')).toEqual('email')
|
|
75
|
+
|
|
76
|
+
expect(input.id).toEqual('email')
|
|
77
|
+
expect(input.type).toEqual('email')
|
|
78
|
+
expect(input.required).toEqual(true)
|
|
79
|
+
expect(input.placeholder).toEqual('Enter your email')
|
|
80
|
+
expect(input.name).toEqual('email')
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('when the input is a phone number', () => {
|
|
85
|
+
const data = {
|
|
86
|
+
label: 'Phone',
|
|
87
|
+
type: 'tel',
|
|
88
|
+
required: true,
|
|
89
|
+
placeholder: 'Enter your phone number',
|
|
90
|
+
kind: 'phone',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
Hellotext.business = {
|
|
95
|
+
country: {
|
|
96
|
+
prefix: '598',
|
|
97
|
+
code: 'UY',
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('builds an article element that contains a label and input elements', () => {
|
|
103
|
+
const article = InputBuilder.build(data)
|
|
104
|
+
const label = article.querySelector('label')
|
|
105
|
+
const input = article.querySelector('input')
|
|
106
|
+
|
|
107
|
+
expect(label.innerText).toEqual('Phone')
|
|
108
|
+
expect(label.getAttribute('for')).toEqual('phone')
|
|
109
|
+
|
|
110
|
+
expect(input.id).toEqual('phone')
|
|
111
|
+
expect(input.type).toEqual('tel')
|
|
112
|
+
expect(input.required).toEqual(true)
|
|
113
|
+
expect(input.placeholder).toEqual('Enter your phone number')
|
|
114
|
+
expect(input.name).toEqual('phone')
|
|
115
|
+
expect(input.value).toEqual('+598')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Event from "../../src/core/event"
|
|
2
|
+
|
|
3
|
+
describe(".valid", function () {
|
|
4
|
+
it("is true when event name is a valid defined name", () => {
|
|
5
|
+
expect(Event.valid("session-set")).toEqual(true)
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("is false when event name is not defined", () => {
|
|
9
|
+
expect(Event.valid("undefined-event")).toEqual(false)
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe(".invalid", () => {
|
|
14
|
+
it("is true when event name is not defined", () => {
|
|
15
|
+
expect(Event.invalid("undefined-event")).toEqual(true)
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("is false when event name is a valid defined name", () => {
|
|
19
|
+
expect(Event.invalid("session-set")).toEqual(false)
|
|
20
|
+
});
|
|
21
|
+
});
|
|
2
22
|
|
|
3
23
|
describe("#addSubscriber", function () {
|
|
4
|
-
const instance = new
|
|
24
|
+
const instance = new Event()
|
|
5
25
|
|
|
6
26
|
it("adds the callback to the list of subscribers for an event", () => {
|
|
7
27
|
const callback = (session) => {}
|
|
@@ -11,7 +31,7 @@ describe("#addSubscriber", function () {
|
|
|
11
31
|
});
|
|
12
32
|
|
|
13
33
|
describe("#removeSubscriber", function () {
|
|
14
|
-
const instance = new
|
|
34
|
+
const instance = new Event()
|
|
15
35
|
|
|
16
36
|
it("removes the callback from the list of subscribers for an event", () => {
|
|
17
37
|
const callback = (session) => {}
|
|
@@ -22,14 +42,14 @@ describe("#removeSubscriber", function () {
|
|
|
22
42
|
});
|
|
23
43
|
});
|
|
24
44
|
|
|
25
|
-
describe("#
|
|
26
|
-
const instance = new
|
|
45
|
+
describe("#dispatch", () => {
|
|
46
|
+
const instance = new Event()
|
|
27
47
|
|
|
28
48
|
it("notifies the listeners for an event", () => {
|
|
29
49
|
const callback = jest.fn()
|
|
30
50
|
|
|
31
51
|
instance.addSubscriber("session-set", callback)
|
|
32
|
-
instance.
|
|
52
|
+
instance.dispatch("session-set", "session_payload")
|
|
33
53
|
|
|
34
54
|
expect(callback).toHaveBeenCalledTimes(1)
|
|
35
55
|
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import Hellotext from "../src/hellotext";
|
|
6
|
+
import { Business } from "../src/models"
|
|
6
7
|
|
|
7
8
|
const getCookieValue = name => document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop()
|
|
8
9
|
|
|
@@ -10,6 +11,10 @@ const expireSession = () => {
|
|
|
10
11
|
document.cookie = "hello_session=;expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
Business.prototype.fetchPublicData = jest.fn().mockResolvedValue({ whitelist: 'disabled' })
|
|
16
|
+
})
|
|
17
|
+
|
|
13
18
|
afterEach(() => {
|
|
14
19
|
jest.clearAllMocks();
|
|
15
20
|
});
|
|
@@ -204,3 +209,10 @@ describe("when hello_preview query parameter is present", () => {
|
|
|
204
209
|
});
|
|
205
210
|
})
|
|
206
211
|
})
|
|
212
|
+
|
|
213
|
+
describe('setSession', () => {
|
|
214
|
+
it('sets the session', () => {
|
|
215
|
+
Hellotext.setSession("12345")
|
|
216
|
+
expect(Hellotext.session).toEqual("12345")
|
|
217
|
+
})
|
|
218
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import { Business } from '../../src/models'
|
|
7
|
+
|
|
8
|
+
describe('enabledWhitelist', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) })
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('is true when the whitelist is an array', () => {
|
|
14
|
+
const business = new Business('123')
|
|
15
|
+
business.data = { whitelist: ['www.example.com'] }
|
|
16
|
+
|
|
17
|
+
expect(business.enabledWhitelist).toEqual(true)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('is false when the whitelist is a string', () => {
|
|
21
|
+
const business = new Business('123')
|
|
22
|
+
business.data = { whitelist: 'disabled' }
|
|
23
|
+
|
|
24
|
+
expect(business.enabledWhitelist).toEqual(false)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Cookies } from '../../src/models'
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
document.cookie = ''
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('set', () => {
|
|
12
|
+
it('sets the value of a cookie', () => {
|
|
13
|
+
expect(Cookies.get('hello_session')).toEqual(undefined)
|
|
14
|
+
|
|
15
|
+
Cookies.set('hello_session', 'session')
|
|
16
|
+
expect(Cookies.get('hello_session')).toEqual('session')
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('get', () => {
|
|
21
|
+
it('gets the value of a cookie', () => {
|
|
22
|
+
document.cookie = 'hello_session=session'
|
|
23
|
+
expect(Cookies.get('hello_session')).toEqual('session')
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Business, FormCollection } from '../../src/models'
|
|
6
|
+
import Hellotext from '../../src/hellotext'
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
Business.prototype.fetchPublicData = jest.fn().mockResolvedValue({ whitelist: 'disabled' })
|
|
10
|
+
|
|
11
|
+
Hellotext.initialize('M01az53K', {
|
|
12
|
+
autogenerateSession: false
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('collect', () => {
|
|
17
|
+
it('does not fetch forms when there are no forms to fetch', async () => {
|
|
18
|
+
const fetch = jest.fn()
|
|
19
|
+
global.fetch = fetch
|
|
20
|
+
|
|
21
|
+
await Hellotext.forms.collect()
|
|
22
|
+
|
|
23
|
+
expect(fetch).not.toHaveBeenCalled()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('fetches forms when there are forms to fetch', async () => {
|
|
27
|
+
global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({ id: 1 }) })
|
|
28
|
+
|
|
29
|
+
document.body.innerHTML = `
|
|
30
|
+
<form data-hello-form="1"></form>
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
await Hellotext.forms.collect()
|
|
34
|
+
|
|
35
|
+
expect(fetch).toHaveBeenCalledTimes(1)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('add', () => {
|
|
40
|
+
const forms = new FormCollection()
|
|
41
|
+
const form = { id: 1, name: 'Form 1' }
|
|
42
|
+
|
|
43
|
+
it('adds a form to the forms array', () => {
|
|
44
|
+
forms.add(form)
|
|
45
|
+
expect(forms.length).toEqual(1)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('does not add a form that is already in the forms array', () => {
|
|
49
|
+
forms.add(form)
|
|
50
|
+
forms.add(form)
|
|
51
|
+
|
|
52
|
+
expect(forms.length).toEqual(1)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('getting elements', () => {
|
|
57
|
+
const forms = new FormCollection()
|
|
58
|
+
forms.add({ id: 1, name: 'Form 1' })
|
|
59
|
+
forms.add({ id: 2, name: 'Form 1' })
|
|
60
|
+
|
|
61
|
+
describe('getById', () => {
|
|
62
|
+
it('returns the form with the given id', () => {
|
|
63
|
+
expect(forms.getById(1).id).toEqual(1)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('getByIndex', () => {
|
|
68
|
+
it('returns the form at the given index', () => {
|
|
69
|
+
expect(forms.getByIndex(1).id).toEqual(2)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('includes', () => {
|
|
75
|
+
const forms = new FormCollection()
|
|
76
|
+
const form = { id: 1, name: 'Form 1' }
|
|
77
|
+
|
|
78
|
+
it('is true when the form is in the forms array', () => {
|
|
79
|
+
forms.add(form)
|
|
80
|
+
expect(forms.includes(1)).toEqual(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('is false when the form is not in the forms array', () => {
|
|
84
|
+
expect(forms.includes(2)).toEqual(false)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('excludes', () => {
|
|
89
|
+
const forms = new FormCollection()
|
|
90
|
+
const form = { id: 1, name: 'Form 1' }
|
|
91
|
+
|
|
92
|
+
it('is true when the form is not in the forms array', () => {
|
|
93
|
+
expect(forms.excludes(2)).toEqual(true)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('is false when the form is in the forms array', () => {
|
|
97
|
+
forms.add(form)
|
|
98
|
+
expect(forms.excludes(1)).toEqual(false)
|
|
99
|
+
})
|
|
100
|
+
})
|