@posthog/core 1.27.9 → 1.28.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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/logs/index.d.ts +94 -4
- package/dist/logs/index.d.ts.map +1 -1
- package/dist/logs/index.js +149 -4
- package/dist/logs/index.mjs +150 -5
- package/dist/logs/logs-utils.d.ts +2 -1
- package/dist/logs/logs-utils.d.ts.map +1 -1
- package/dist/logs/types.d.ts +140 -8
- package/dist/logs/types.d.ts.map +1 -1
- package/dist/posthog-core-stateless.d.ts +34 -0
- package/dist/posthog-core-stateless.d.ts.map +1 -1
- package/dist/posthog-core-stateless.js +46 -0
- package/dist/posthog-core-stateless.mjs +46 -0
- package/dist/posthog-core.d.ts.map +1 -1
- package/dist/posthog-core.js +2 -0
- package/dist/posthog-core.mjs +2 -0
- package/dist/surveys/events.d.ts +22 -0
- package/dist/surveys/events.d.ts.map +1 -0
- package/dist/surveys/events.js +95 -0
- package/dist/surveys/events.mjs +43 -0
- package/dist/surveys/index.d.ts +4 -0
- package/dist/surveys/index.d.ts.map +1 -0
- package/dist/surveys/index.js +83 -0
- package/dist/surveys/index.mjs +4 -0
- package/dist/surveys/translations.d.ts +38 -0
- package/dist/surveys/translations.d.ts.map +1 -0
- package/dist/surveys/translations.js +207 -0
- package/dist/surveys/translations.mjs +158 -0
- package/dist/testing/test-utils.d.ts.map +1 -1
- package/dist/testing/test-utils.js +1 -0
- package/dist/testing/test-utils.mjs +1 -0
- package/dist/types.d.ts +31 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -0
- package/dist/utils/logger.mjs +3 -0
- package/package.json +26 -2
- package/src/index.ts +12 -1
- package/src/logs/index.spec.ts +891 -17
- package/src/logs/index.ts +341 -11
- package/src/logs/logs-utils.spec.ts +2 -1
- package/src/logs/logs-utils.ts +1 -1
- package/src/logs/types.ts +150 -25
- package/src/posthog-core-stateless.ts +80 -0
- package/src/posthog-core.ts +6 -0
- package/src/surveys/events.spec.ts +52 -0
- package/src/surveys/events.ts +80 -0
- package/src/surveys/index.ts +18 -0
- package/src/surveys/translations.spec.ts +205 -0
- package/src/surveys/translations.ts +244 -0
- package/src/testing/test-utils.ts +1 -0
- package/src/types.ts +38 -2
- package/src/utils/logger.ts +6 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OtlpLogsPayload } from '@posthog/types'
|
|
1
2
|
import { SimpleEventEmitter } from './eventemitter'
|
|
2
3
|
import { getFeatureFlagValue, normalizeFlagsResponse } from './featureFlagUtils'
|
|
3
4
|
import { gzipCompress, isGzipSupported } from './gzip'
|
|
@@ -94,6 +95,23 @@ function isPostHogFetchContentTooLargeError(err: unknown): err is PostHogFetchHt
|
|
|
94
95
|
return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Outcome of a logs batch send. Keeps HTTP error classification inside core
|
|
100
|
+
* (single source of truth — same policy events already use in `_flush()`) so
|
|
101
|
+
* PostHogLogs doesn't need to know about specific error types.
|
|
102
|
+
*
|
|
103
|
+
* - ok → records are accepted; drop them from the queue
|
|
104
|
+
* - too-large → 413; caller should halve batch size and retry same records
|
|
105
|
+
* - retry-later → network error; caller keeps records and retries next cycle
|
|
106
|
+
* - fatal → anything else (auth, malformed, etc.); caller drops the
|
|
107
|
+
* batch and surfaces the error
|
|
108
|
+
*/
|
|
109
|
+
export type SendLogsBatchOutcome =
|
|
110
|
+
| { kind: 'ok' }
|
|
111
|
+
| { kind: 'too-large' }
|
|
112
|
+
| { kind: 'retry-later'; error: unknown }
|
|
113
|
+
| { kind: 'fatal'; error: unknown }
|
|
114
|
+
|
|
97
115
|
export enum QuotaLimitedFeature {
|
|
98
116
|
FeatureFlags = 'feature_flags',
|
|
99
117
|
Recordings = 'recordings',
|
|
@@ -789,6 +807,10 @@ export abstract class PostHogCoreStateless {
|
|
|
789
807
|
public async getSurveysStateless(): Promise<SurveyResponse['surveys']> {
|
|
790
808
|
await this._initPromise
|
|
791
809
|
|
|
810
|
+
if (this.disabled) {
|
|
811
|
+
return []
|
|
812
|
+
}
|
|
813
|
+
|
|
792
814
|
if (this.disableSurveys === true) {
|
|
793
815
|
this._logger.info('Loading surveys is disabled.')
|
|
794
816
|
return []
|
|
@@ -1053,6 +1075,10 @@ export abstract class PostHogCoreStateless {
|
|
|
1053
1075
|
* @throws Error
|
|
1054
1076
|
*/
|
|
1055
1077
|
async flush(): Promise<void> {
|
|
1078
|
+
if (this.disabled) {
|
|
1079
|
+
return
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1056
1082
|
// Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
|
|
1057
1083
|
// Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
|
|
1058
1084
|
// Use a custom allSettled implementation to avoid issues with patching Promise on RN
|
|
@@ -1179,6 +1205,56 @@ export abstract class PostHogCoreStateless {
|
|
|
1179
1205
|
this._events.emit('flush', sentMessages)
|
|
1180
1206
|
}
|
|
1181
1207
|
|
|
1208
|
+
/**
|
|
1209
|
+
* Sends a pre-built OTLP logs payload to `/i/v1/logs`. Returns a tagged
|
|
1210
|
+
* outcome instead of throwing so PostHogLogs doesn't have to know about the
|
|
1211
|
+
* core's error class hierarchy. Error classification lives here (single
|
|
1212
|
+
* source of truth, same policy the events `_flush()` uses for its own
|
|
1213
|
+
* 413 / network / fatal handling).
|
|
1214
|
+
*
|
|
1215
|
+
* 413 is passed through as `too-large` (not auto-retried) so the caller can
|
|
1216
|
+
* shrink `maxBatchRecordsPerPost` and retry the same records.
|
|
1217
|
+
*/
|
|
1218
|
+
async _sendLogsBatch(payload: OtlpLogsPayload): Promise<SendLogsBatchOutcome> {
|
|
1219
|
+
if (this.disabled) {
|
|
1220
|
+
return { kind: 'fatal', error: new Error('The client is disabled') }
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const serialized = JSON.stringify(payload)
|
|
1224
|
+
const url = `${this.host}/i/v1/logs?token=${encodeURIComponent(this.apiKey)}`
|
|
1225
|
+
|
|
1226
|
+
const gzippedPayload = !this.disableCompression ? await gzipCompress(serialized, this.isDebug) : null
|
|
1227
|
+
const fetchOptions: PostHogFetchOptions = {
|
|
1228
|
+
method: 'POST',
|
|
1229
|
+
headers: {
|
|
1230
|
+
...this.getCustomHeaders(),
|
|
1231
|
+
'Content-Type': 'application/json',
|
|
1232
|
+
...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
|
|
1233
|
+
},
|
|
1234
|
+
body: gzippedPayload || serialized,
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
try {
|
|
1238
|
+
await this.fetchWithRetry(url, fetchOptions, {
|
|
1239
|
+
retryCheck: (err) => {
|
|
1240
|
+
if (isPostHogFetchContentTooLargeError(err)) {
|
|
1241
|
+
return false
|
|
1242
|
+
}
|
|
1243
|
+
return isPostHogFetchError(err)
|
|
1244
|
+
},
|
|
1245
|
+
})
|
|
1246
|
+
return { kind: 'ok' }
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
if (isPostHogFetchContentTooLargeError(err)) {
|
|
1249
|
+
return { kind: 'too-large' }
|
|
1250
|
+
}
|
|
1251
|
+
if (err instanceof PostHogFetchNetworkError) {
|
|
1252
|
+
return { kind: 'retry-later', error: err }
|
|
1253
|
+
}
|
|
1254
|
+
return { kind: 'fatal', error: err }
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1182
1258
|
private async fetchWithRetry(
|
|
1183
1259
|
url: string,
|
|
1184
1260
|
options: PostHogFetchOptions,
|
|
@@ -1241,6 +1317,10 @@ export abstract class PostHogCoreStateless {
|
|
|
1241
1317
|
let hasTimedOut = false
|
|
1242
1318
|
this.clearFlushTimer()
|
|
1243
1319
|
|
|
1320
|
+
if (this.disabled) {
|
|
1321
|
+
return
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1244
1324
|
const doShutdown = async (): Promise<void> => {
|
|
1245
1325
|
try {
|
|
1246
1326
|
await this.promiseQueue.join()
|
package/src/posthog-core.ts
CHANGED
|
@@ -566,6 +566,9 @@ export abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
566
566
|
|
|
567
567
|
private async remoteConfigAsync(): Promise<PostHogRemoteConfig | undefined> {
|
|
568
568
|
await this._initPromise
|
|
569
|
+
if (this.disabled) {
|
|
570
|
+
return undefined
|
|
571
|
+
}
|
|
569
572
|
if (this._remoteConfigResponsePromise) {
|
|
570
573
|
return this._remoteConfigResponsePromise
|
|
571
574
|
}
|
|
@@ -578,6 +581,9 @@ export abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
578
581
|
protected async flagsAsync(options?: FlagsAsyncOptions): Promise<PostHogFeatureFlagsResponse | undefined> {
|
|
579
582
|
const { sendAnonDistinctId = true, fetchConfig = false, triggerOnRemoteConfig = false } = options ?? {}
|
|
580
583
|
await this._initPromise
|
|
584
|
+
if (this.disabled) {
|
|
585
|
+
return undefined
|
|
586
|
+
}
|
|
581
587
|
if (this._flagsResponsePromise) {
|
|
582
588
|
// Queue the reload request instead of dropping it
|
|
583
589
|
// This ensures that requests with $anon_distinct_id (from identify()) are not lost
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
|
+
import {
|
|
3
|
+
buildSurveyResponseProperties,
|
|
4
|
+
getSurveyInteractionProperty,
|
|
5
|
+
getSurveyResponseKey,
|
|
6
|
+
getSurveyResponseValue,
|
|
7
|
+
surveyHasResponses,
|
|
8
|
+
} from './events'
|
|
9
|
+
|
|
10
|
+
describe('survey event helpers', () => {
|
|
11
|
+
const survey = {
|
|
12
|
+
id: 'survey-1',
|
|
13
|
+
current_iteration: 2,
|
|
14
|
+
questions: [
|
|
15
|
+
{ id: 'q1', question: 'Rate us', originalQuestionIndex: 0 },
|
|
16
|
+
{ id: 'q2', question: 'Anything else?', originalQuestionIndex: 1 },
|
|
17
|
+
],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
it('builds response properties with current and legacy response keys', () => {
|
|
21
|
+
const responses = {
|
|
22
|
+
[getSurveyResponseKey('q1')]: 5,
|
|
23
|
+
[getSurveyResponseKey('q2')]: ['fast', 'clear'],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
expect(buildSurveyResponseProperties(responses, survey)).toEqual({
|
|
27
|
+
$survey_questions: [
|
|
28
|
+
{ id: 'q1', question: 'Rate us', response: 5 },
|
|
29
|
+
{ id: 'q2', question: 'Anything else?', response: ['fast', 'clear'] },
|
|
30
|
+
],
|
|
31
|
+
$survey_response_q1: 5,
|
|
32
|
+
$survey_response_q2: ['fast', 'clear'],
|
|
33
|
+
$survey_response: 5,
|
|
34
|
+
$survey_response_1: ['fast', 'clear'],
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('copies array response values before returning them', () => {
|
|
39
|
+
const responses = { [getSurveyResponseKey('q1')]: ['a'] }
|
|
40
|
+
|
|
41
|
+
const response = getSurveyResponseValue(responses, 'q1')
|
|
42
|
+
|
|
43
|
+
expect(response).toEqual(['a'])
|
|
44
|
+
expect(response).not.toBe(responses.$survey_response_q1)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('detects non-nullish responses and builds interaction property names', () => {
|
|
48
|
+
expect(surveyHasResponses({ $survey_response_q1: null })).toBe(false)
|
|
49
|
+
expect(surveyHasResponses({ $survey_response_q1: 0 })).toBe(true)
|
|
50
|
+
expect(getSurveyInteractionProperty(survey, 'responded')).toBe('$survey_responded/survey-1/2')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { SurveyResponses, SurveyResponseValue } from '../types'
|
|
2
|
+
import { isArray, isNullish, isUndefined } from '../utils'
|
|
3
|
+
|
|
4
|
+
export const SURVEY_LANGUAGE_PROPERTY = '$survey_language'
|
|
5
|
+
|
|
6
|
+
export function getSurveyResponseKey(questionId: string): string {
|
|
7
|
+
return `$survey_response_${questionId}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getSurveyOldResponseKey(originalQuestionIndex: number): string {
|
|
11
|
+
return originalQuestionIndex === 0 ? '$survey_response' : `$survey_response_${originalQuestionIndex}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getSurveyResponseValue(
|
|
15
|
+
responses: SurveyResponses,
|
|
16
|
+
questionId?: string
|
|
17
|
+
): SurveyResponseValue | undefined {
|
|
18
|
+
if (!questionId) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
const response = responses[getSurveyResponseKey(questionId)]
|
|
22
|
+
if (isArray(response)) {
|
|
23
|
+
return [...response]
|
|
24
|
+
}
|
|
25
|
+
return response
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildSurveyResponseProperties(
|
|
29
|
+
responses: SurveyResponses = {},
|
|
30
|
+
survey: SurveyForResponses
|
|
31
|
+
): Record<string, unknown> {
|
|
32
|
+
const oldFormatResponses: SurveyResponses = {}
|
|
33
|
+
survey.questions.forEach((question: SurveyQuestionForResponses) => {
|
|
34
|
+
if (isUndefined(question.originalQuestionIndex)) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const oldResponseKey = getSurveyOldResponseKey(question.originalQuestionIndex)
|
|
38
|
+
const response = getSurveyResponseValue(responses, question.id)
|
|
39
|
+
if (!isUndefined(response)) {
|
|
40
|
+
oldFormatResponses[oldResponseKey] = response
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
$survey_questions: survey.questions.map((question: SurveyQuestionForResponses) => ({
|
|
46
|
+
id: question.id,
|
|
47
|
+
question: question.question,
|
|
48
|
+
response: getSurveyResponseValue(responses, question.id),
|
|
49
|
+
})),
|
|
50
|
+
...responses,
|
|
51
|
+
...oldFormatResponses,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function surveyHasResponses(responses: SurveyResponses = {}): boolean {
|
|
56
|
+
return Object.values(responses).some((response) => !isNullish(response))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getSurveyInteractionProperty(survey: SurveyWithIteration, action: string): string {
|
|
60
|
+
let surveyProperty = `$survey_${action}/${survey.id}`
|
|
61
|
+
if (survey.current_iteration && survey.current_iteration > 0) {
|
|
62
|
+
surveyProperty = `$survey_${action}/${survey.id}/${survey.current_iteration}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return surveyProperty
|
|
66
|
+
}
|
|
67
|
+
type SurveyQuestionForResponses = {
|
|
68
|
+
id?: string
|
|
69
|
+
question: string
|
|
70
|
+
originalQuestionIndex?: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type SurveyForResponses = {
|
|
74
|
+
questions: SurveyQuestionForResponses[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type SurveyWithIteration = {
|
|
78
|
+
id: string
|
|
79
|
+
current_iteration?: number | null
|
|
80
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { getValidationError, getLengthFromRules, getRequirementsHint } from './validation'
|
|
2
|
+
export {
|
|
3
|
+
buildSurveyResponseProperties,
|
|
4
|
+
getSurveyInteractionProperty,
|
|
5
|
+
getSurveyOldResponseKey,
|
|
6
|
+
getSurveyResponseKey,
|
|
7
|
+
getSurveyResponseValue,
|
|
8
|
+
SURVEY_LANGUAGE_PROPERTY,
|
|
9
|
+
surveyHasResponses,
|
|
10
|
+
} from './events'
|
|
11
|
+
export {
|
|
12
|
+
applySurveyTranslation,
|
|
13
|
+
detectSurveyLanguage,
|
|
14
|
+
findBestTranslationMatch,
|
|
15
|
+
getBaseLanguage,
|
|
16
|
+
getLanguageFromStoredPersonProperties,
|
|
17
|
+
normalizeLanguageCode,
|
|
18
|
+
} from './translations'
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals'
|
|
2
|
+
import {
|
|
3
|
+
applySurveyTranslation,
|
|
4
|
+
detectSurveyLanguage,
|
|
5
|
+
findBestTranslationMatch,
|
|
6
|
+
getLanguageFromStoredPersonProperties,
|
|
7
|
+
} from './translations'
|
|
8
|
+
import { Survey, SurveyQuestionType, SurveyType } from '../types'
|
|
9
|
+
|
|
10
|
+
const createBaseSurvey = (): Survey => ({
|
|
11
|
+
id: 'test-survey',
|
|
12
|
+
name: 'Test Survey',
|
|
13
|
+
description: 'Test Description',
|
|
14
|
+
type: SurveyType.Popover,
|
|
15
|
+
questions: [
|
|
16
|
+
{
|
|
17
|
+
type: SurveyQuestionType.Open,
|
|
18
|
+
question: 'What do you think?',
|
|
19
|
+
id: 'q1',
|
|
20
|
+
originalQuestionIndex: 0,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
appearance: {
|
|
24
|
+
thankYouMessageHeader: 'Thank you!',
|
|
25
|
+
thankYouMessageDescription: 'We appreciate your feedback',
|
|
26
|
+
thankYouMessageCloseButtonText: 'Close',
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('survey translations', () => {
|
|
31
|
+
describe('detectSurveyLanguage', () => {
|
|
32
|
+
it.each([
|
|
33
|
+
{
|
|
34
|
+
name: 'prioritizes override language',
|
|
35
|
+
input: {
|
|
36
|
+
overrideLanguage: 'de',
|
|
37
|
+
storedPersonProperties: { language: 'es' },
|
|
38
|
+
locale: 'fr',
|
|
39
|
+
},
|
|
40
|
+
expected: 'de',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'uses person language when override is missing',
|
|
44
|
+
input: {
|
|
45
|
+
storedPersonProperties: { language: 'es' },
|
|
46
|
+
locale: 'fr',
|
|
47
|
+
},
|
|
48
|
+
expected: 'es',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'falls back to locale',
|
|
52
|
+
input: {
|
|
53
|
+
storedPersonProperties: { some_other_property: 'value' },
|
|
54
|
+
locale: 'fr-CA',
|
|
55
|
+
},
|
|
56
|
+
expected: 'fr-CA',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'returns null when no sources exist',
|
|
60
|
+
input: {
|
|
61
|
+
storedPersonProperties: { some_other_property: 'value' },
|
|
62
|
+
},
|
|
63
|
+
expected: null,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'trims override language',
|
|
67
|
+
input: {
|
|
68
|
+
overrideLanguage: ' es ',
|
|
69
|
+
},
|
|
70
|
+
expected: 'es',
|
|
71
|
+
},
|
|
72
|
+
])('$name', ({ input, expected }) => {
|
|
73
|
+
expect(detectSurveyLanguage(input)).toBe(expected)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('reads language from stored person properties', () => {
|
|
77
|
+
expect(getLanguageFromStoredPersonProperties({ language: 'it' })).toBe('it')
|
|
78
|
+
expect(getLanguageFromStoredPersonProperties({ language: ' ' })).toBeNull()
|
|
79
|
+
expect(getLanguageFromStoredPersonProperties({})).toBeNull()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe('findBestTranslationMatch', () => {
|
|
84
|
+
it('supports exact and base-language matches', () => {
|
|
85
|
+
expect(findBestTranslationMatch({ fr: {}, 'fr-CA': {} }, 'FR-ca')).toBe('fr-CA')
|
|
86
|
+
expect(findBestTranslationMatch({ fr: {} }, 'fr-CA')).toBe('fr')
|
|
87
|
+
expect(findBestTranslationMatch({ es: {} }, 'de')).toBeNull()
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('applySurveyTranslation', () => {
|
|
92
|
+
it('returns original survey when no translations exist', () => {
|
|
93
|
+
const survey = createBaseSurvey()
|
|
94
|
+
const result = applySurveyTranslation(survey, 'fr')
|
|
95
|
+
|
|
96
|
+
expect(result.survey).toEqual(survey)
|
|
97
|
+
expect(result.matchedKey).toBeNull()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('applies survey-level translations', () => {
|
|
101
|
+
const survey = createBaseSurvey()
|
|
102
|
+
survey.translations = {
|
|
103
|
+
fr: {
|
|
104
|
+
name: 'Enquete Test',
|
|
105
|
+
thankYouMessageHeader: 'Merci',
|
|
106
|
+
thankYouMessageDescription: 'Merci pour votre retour',
|
|
107
|
+
thankYouMessageCloseButtonText: 'Fermer',
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = applySurveyTranslation(survey, 'fr')
|
|
112
|
+
|
|
113
|
+
expect(result.survey.name).toBe('Enquete Test')
|
|
114
|
+
expect(result.survey.appearance?.thankYouMessageHeader).toBe('Merci')
|
|
115
|
+
expect(result.survey.appearance?.thankYouMessageDescription).toBe('Merci pour votre retour')
|
|
116
|
+
expect(result.survey.appearance?.thankYouMessageCloseButtonText).toBe('Fermer')
|
|
117
|
+
expect(result.matchedKey).toBe('fr')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('applies question-level translations', () => {
|
|
121
|
+
const survey = createBaseSurvey()
|
|
122
|
+
survey.questions = [
|
|
123
|
+
{
|
|
124
|
+
type: SurveyQuestionType.Rating,
|
|
125
|
+
question: 'How was it?',
|
|
126
|
+
id: 'q1',
|
|
127
|
+
originalQuestionIndex: 0,
|
|
128
|
+
display: 'number',
|
|
129
|
+
scale: 5,
|
|
130
|
+
lowerBoundLabel: 'Bad',
|
|
131
|
+
upperBoundLabel: 'Great',
|
|
132
|
+
translations: {
|
|
133
|
+
pt: {
|
|
134
|
+
question: 'Como foi?',
|
|
135
|
+
lowerBoundLabel: 'Ruim',
|
|
136
|
+
upperBoundLabel: 'Otimo',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: SurveyQuestionType.MultipleChoice,
|
|
142
|
+
question: 'Pick one',
|
|
143
|
+
id: 'q2',
|
|
144
|
+
originalQuestionIndex: 1,
|
|
145
|
+
choices: ['One', 'Other'],
|
|
146
|
+
hasOpenChoice: true,
|
|
147
|
+
translations: {
|
|
148
|
+
pt: {
|
|
149
|
+
choices: ['Um', 'Outro'],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
const result = applySurveyTranslation(survey, 'pt-BR')
|
|
156
|
+
|
|
157
|
+
expect(result.survey.questions[0].question).toBe('Como foi?')
|
|
158
|
+
expect('lowerBoundLabel' in result.survey.questions[0] && result.survey.questions[0].lowerBoundLabel).toBe('Ruim')
|
|
159
|
+
expect('upperBoundLabel' in result.survey.questions[0] && result.survey.questions[0].upperBoundLabel).toBe(
|
|
160
|
+
'Otimo'
|
|
161
|
+
)
|
|
162
|
+
expect('choices' in result.survey.questions[1] && result.survey.questions[1].choices).toEqual(['Um', 'Outro'])
|
|
163
|
+
expect(result.matchedKey).toBe('pt')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('uses question-level match when there is no survey-level translation', () => {
|
|
167
|
+
const survey = createBaseSurvey()
|
|
168
|
+
survey.questions[0].translations = {
|
|
169
|
+
de: {
|
|
170
|
+
question: 'Was denkst du?',
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const result = applySurveyTranslation(survey, 'de')
|
|
175
|
+
|
|
176
|
+
expect(result.survey.questions[0].question).toBe('Was denkst du?')
|
|
177
|
+
expect(result.matchedKey).toBe('de')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('preserves custom survey and question fields for shared consumers', () => {
|
|
181
|
+
const survey = {
|
|
182
|
+
name: 'Custom Survey',
|
|
183
|
+
customSurveyField: true,
|
|
184
|
+
questions: [
|
|
185
|
+
{
|
|
186
|
+
question: 'Pick one',
|
|
187
|
+
customQuestionField: 'native-renderer',
|
|
188
|
+
translations: {
|
|
189
|
+
fr: {
|
|
190
|
+
question: 'Choisissez une option',
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const result = applySurveyTranslation(survey, 'fr')
|
|
198
|
+
|
|
199
|
+
expect(result.survey.customSurveyField).toBe(true)
|
|
200
|
+
expect(result.survey.questions[0].question).toBe('Choisissez une option')
|
|
201
|
+
expect(result.survey.questions[0].customQuestionField).toBe('native-renderer')
|
|
202
|
+
expect(result.matchedKey).toBe('fr')
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|