Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ const bodyHasVariables = (template: WhatsAppMessageTemplate): boolean => {
return body ? body.text.match(VARIABLE_REGEX) !== null : false;
};

const textHeaderVariables = (template: WhatsAppMessageTemplate): string[] => {
const header = findComponentByType(template, 'HEADER');
if (header?.format !== 'TEXT' || !header.text) return [];

return extractVariables(header.text);
};

// Collects the URL/COPY_CODE buttons that require a user-supplied parameter,
// preserving their positional index (sparse array).
const buildButtonParams = (
Expand Down Expand Up @@ -158,22 +165,20 @@ const buildButtonParams = (
};

/**
* Builds the empty WhatsApp processed_params scaffold for a template: body keys,
* a media header block, and the button parameters that need filling.
* Builds the empty WhatsApp processed_params scaffold for a template: text or
* media header values, body keys, and button parameters that need filling.
*/
export const buildWhatsAppProcessedParams = (
template: WhatsAppMessageTemplate
): WhatsAppProcessedParams => {
const params: WhatsAppProcessedParams = {};

const body = findComponentByType(template, 'BODY');
if (!body) return params;

const matchedVariables = body.text.match(VARIABLE_REGEX);
if (matchedVariables) {
const bodyVariables = body ? extractVariables(body.text) : [];
if (bodyVariables.length > 0) {
const bodyParams: Record<string, string> = {};
matchedVariables.forEach(variable => {
bodyParams[processVariable(variable)] = '';
bodyVariables.forEach(variable => {
bodyParams[variable] = '';
});
params.body = bodyParams;
}
Expand All @@ -182,6 +187,14 @@ export const buildWhatsAppProcessedParams = (
const format = getMediaType(template);
params.header = { media_url: '', media_type: format };
if (format === 'document') params.header.media_name = '';
} else {
const headerVariables = textHeaderVariables(template);
if (headerVariables.length > 0) {
params.header = {};
headerVariables.forEach(variable => {
params.header![variable] = '';
});
}
}

const buttons = buildButtonParams(template);
Expand All @@ -191,22 +204,27 @@ export const buildWhatsAppProcessedParams = (
};

/**
* Whether every required WhatsApp input has been filled. A template with no body
* variables and no media header is considered complete even if it has buttons
* (mirrors the composer's validation).
* Whether every required WhatsApp input has been filled. A template with no
* text variables and no media header is considered complete even if it has
* buttons (mirrors the composer's validation).
*/
export const isWhatsAppComplete = (
template: WhatsAppMessageTemplate,
processedParams: WhatsAppProcessedParams
): boolean => {
const hasVariables = bodyHasVariables(template);
const hasBodyVariables = bodyHasVariables(template);
const headerVariables = textHeaderVariables(template);
const media = hasMediaHeader(template);

if (!hasVariables && !media) return true;
if (!hasBodyVariables && headerVariables.length === 0 && !media) return true;

if (media && !processedParams.header?.media_url) return false;

if (hasVariables && processedParams.body) {
if (headerVariables.some(variable => !processedParams.header?.[variable])) {
return false;
}

if (hasBodyVariables && processedParams.body) {
const hasEmptyBodyVariable = Object.keys(processedParams.body).some(
key => !processedParams.body?.[key]
);
Expand Down
5 changes: 3 additions & 2 deletions src/types/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ export interface TemplateButtonParam {
export interface WhatsAppProcessedParams {
body?: Record<string, string>;
header?: {
media_url: string;
media_type: string;
[key: string]: string | undefined;
media_url?: string;
media_type?: string;
media_name?: string;
};
buttons?: TemplateButtonParam[];
Expand Down
33 changes: 33 additions & 0 deletions test/template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,22 @@ describe('#buildWhatsAppProcessedParams', () => {
buildWhatsAppProcessedParams(whatsAppTemplate({ components: [] }))
).toEqual({});
});

it('builds component-scoped parameters for text header and body variables', () => {
const params = buildWhatsAppProcessedParams(
whatsAppTemplate({
components: [
{ type: 'HEADER', format: 'TEXT', text: 'Welcome {{1}}' },
{ type: 'BODY', text: 'Hello {{1}}, your manager is {{2}}.' },
],
})
);

expect(params).toEqual({
header: { '1': '' },
body: { '1': '', '2': '' },
});
});
});

describe('#isWhatsAppComplete', () => {
Expand Down Expand Up @@ -212,6 +228,23 @@ describe('#isWhatsAppComplete', () => {
true
);
});

it('requires text header and body variables', () => {
const textHeader = whatsAppTemplate({
components: [
{ type: 'HEADER', format: 'TEXT', text: 'Welcome {{1}}' },
{ type: 'BODY', text: 'Hello {{1}}, your manager is {{2}}.' },
],
});
const params = buildWhatsAppProcessedParams(textHeader);

params.body!['1'] = 'Ahmad';
params.body!['2'] = 'Furqan';
expect(isWhatsAppComplete(textHeader, params)).toBe(false);

params.header!['1'] = 'Ahmad';
expect(isWhatsAppComplete(textHeader, params)).toBe(true);
});
});

describe('twilio helpers', () => {
Expand Down
Loading