From 170f71d4331f7e98762d1002b729bc99e9b06106 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:38:17 +0300 Subject: [PATCH 01/13] feat (whatsapp): Implement `whatsapp` button attribute --- packages/attributes/package.json | 3 +- packages/attributes/src/load.ts | 4 +++ packages/utils/src/constants/attributes.ts | 2 ++ packages/whatsapp/README.md | 3 ++ packages/whatsapp/api/examples.ts | 5 +++ packages/whatsapp/api/schema.ts | 8 +++++ packages/whatsapp/package.json | 17 ++++++++++ packages/whatsapp/src/factory.ts | 23 ++++++++++++++ packages/whatsapp/src/index.ts | 3 ++ packages/whatsapp/src/init.ts | 24 +++++++++++++++ packages/whatsapp/src/utils/constants.ts | 36 ++++++++++++++++++++++ packages/whatsapp/src/utils/helpers.ts | 8 +++++ packages/whatsapp/src/utils/selectors.ts | 9 ++++++ packages/whatsapp/tsconfig.json | 3 ++ pnpm-lock.yaml | 11 ++++++- 15 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 packages/whatsapp/README.md create mode 100644 packages/whatsapp/api/examples.ts create mode 100644 packages/whatsapp/api/schema.ts create mode 100644 packages/whatsapp/package.json create mode 100644 packages/whatsapp/src/factory.ts create mode 100644 packages/whatsapp/src/index.ts create mode 100644 packages/whatsapp/src/init.ts create mode 100644 packages/whatsapp/src/utils/constants.ts create mode 100644 packages/whatsapp/src/utils/helpers.ts create mode 100644 packages/whatsapp/src/utils/selectors.ts create mode 100644 packages/whatsapp/tsconfig.json diff --git a/packages/attributes/package.json b/packages/attributes/package.json index 6e79c6b52..8dd3e53b3 100644 --- a/packages/attributes/package.json +++ b/packages/attributes/package.json @@ -81,7 +81,8 @@ "@finsweet/attributes-starrating": "workspace:*", "@finsweet/attributes-toc": "workspace:*", "@finsweet/attributes-utils": "workspace:*", - "@finsweet/attributes-videohls": "workspace:*" + "@finsweet/attributes-videohls": "workspace:*", + "@finsweet/attributes-whatsapp": "workspace:*" }, "devDependencies": { "@types/node": "^18.16.1", diff --git a/packages/attributes/src/load.ts b/packages/attributes/src/load.ts index f97957a84..01e2b87a0 100644 --- a/packages/attributes/src/load.ts +++ b/packages/attributes/src/load.ts @@ -182,6 +182,10 @@ export const loadAttribute = async (solution: FsAttributeKey) => { return import('@finsweet/attributes-videohls'); } + case 'whatsapp': { + return import('@finsweet/attributes-whatsapp'); + } + default: { throw `Finsweet Attribute "${solution}" is not supported.`; } diff --git a/packages/utils/src/constants/attributes.ts b/packages/utils/src/constants/attributes.ts index a838ebd1d..252a382b7 100644 --- a/packages/utils/src/constants/attributes.ts +++ b/packages/utils/src/constants/attributes.ts @@ -95,3 +95,5 @@ export const TOC_ATTRIBUTE = 'toc'; export const READ_TIME_ATTRIBUTE = 'readtime'; export const VIDEO_HLS_ATTRIBUTE = 'videohls'; + +export const WHATSAPP_ATTRIBUTE = 'whatsapp'; diff --git a/packages/whatsapp/README.md b/packages/whatsapp/README.md new file mode 100644 index 000000000..d99ad88c2 --- /dev/null +++ b/packages/whatsapp/README.md @@ -0,0 +1,3 @@ +# `whatsapp` Attribute + +Adds a simple url to an anchor element that creates a new whatsapp chat in a new tab when clicked. diff --git a/packages/whatsapp/api/examples.ts b/packages/whatsapp/api/examples.ts new file mode 100644 index 000000000..ce6cd2742 --- /dev/null +++ b/packages/whatsapp/api/examples.ts @@ -0,0 +1,5 @@ +import type { AttributeExamples } from '@finsweet/attributes-utils'; + +const examples: AttributeExamples = []; + +export default examples; diff --git a/packages/whatsapp/api/schema.ts b/packages/whatsapp/api/schema.ts new file mode 100644 index 000000000..3b22a2860 --- /dev/null +++ b/packages/whatsapp/api/schema.ts @@ -0,0 +1,8 @@ +import type { AttributeSchema } from '@finsweet/attributes-utils'; + +const schema: AttributeSchema = { + elements: [], + settings: [], +}; + +export default schema; diff --git a/packages/whatsapp/package.json b/packages/whatsapp/package.json new file mode 100644 index 000000000..76ea712dc --- /dev/null +++ b/packages/whatsapp/package.json @@ -0,0 +1,17 @@ +{ + "name": "@finsweet/attributes-whatsapp", + "version": "1.0.0", + "description": "Adds a simple url to an anchor element that creates a new whatsapp chat in a new tab when clicked.", + "private": true, + "type": "module", + "types": "src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + } + }, + "dependencies": { + "@finsweet/attributes-utils": "workspace:*" + } +} diff --git a/packages/whatsapp/src/factory.ts b/packages/whatsapp/src/factory.ts new file mode 100644 index 000000000..859fb729a --- /dev/null +++ b/packages/whatsapp/src/factory.ts @@ -0,0 +1,23 @@ +import { WHATSAPP_BASE_URL } from './utils/constants'; +import { formatUrl } from './utils/helpers'; +import { getAttribute, getInstanceIndex, queryElement } from './utils/selectors'; + +export const initWhatsappInstance = (buttonElement: Element) => { + // static + let phone = getAttribute(buttonElement, 'phone'); + let message = getAttribute(buttonElement, 'message'); + + // dynamic + if (!message && !phone) { + const instanceIndex = getInstanceIndex(buttonElement); + // phone + phone = queryElement('phone', { instanceIndex, scope: buttonElement })?.textContent ?? ''; + // message + message = queryElement('message', { instanceIndex, scope: buttonElement })?.textContent ?? ''; + } + + if (!phone || !message) return; //todo: throw new Error('Missing phone or message attribute'); + + buttonElement.setAttribute('href', formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message })); + buttonElement.setAttribute('target', '_blank'); +}; diff --git a/packages/whatsapp/src/index.ts b/packages/whatsapp/src/index.ts new file mode 100644 index 000000000..76864f6e2 --- /dev/null +++ b/packages/whatsapp/src/index.ts @@ -0,0 +1,3 @@ +export { version } from '../package.json'; +export { init } from './init'; +export { ELEMENTS, SETTINGS } from './utils/constants'; diff --git a/packages/whatsapp/src/init.ts b/packages/whatsapp/src/init.ts new file mode 100644 index 000000000..8b2b5a852 --- /dev/null +++ b/packages/whatsapp/src/init.ts @@ -0,0 +1,24 @@ +import { type FsAttributeInit, isNotEmpty, waitWebflowReady } from '@finsweet/attributes-utils'; + +import { initWhatsappInstance } from './factory'; +import { queryAllElements } from './utils/selectors'; + +/** + * Inits the attribute. + */ +export const init: FsAttributeInit = async () => { + await waitWebflowReady(); + + // Get all whatsapp buttons on the page + const buttonElements = queryAllElements('button'); + + // Create instances of each whatsapp button + buttonElements.map(initWhatsappInstance).filter(isNotEmpty); + + return { + result: buttonElements, + destroy() { + return; + }, + }; +}; diff --git a/packages/whatsapp/src/utils/constants.ts b/packages/whatsapp/src/utils/constants.ts new file mode 100644 index 000000000..0d133191f --- /dev/null +++ b/packages/whatsapp/src/utils/constants.ts @@ -0,0 +1,36 @@ +import { type AttributeElements, type AttributeSettings } from '@finsweet/attributes-utils'; + +export const ELEMENTS = [ + /** + * Defines a button element. + */ + 'button', + /** + * Defines a phone element + */ + 'phone', + /** + * Defines a message element. + */ + 'message', +] as const satisfies AttributeElements; + +export const SETTINGS = { + /** + * Defines the WhatsApp phone number. + */ + phone: { + key: 'phone', + }, + /** + * Defines the WhatsApp message. + */ + message: { + key: 'message', + }, +} as const satisfies AttributeSettings; + +/** + * Defines the WhatsApp base URL. + */ +export const WHATSAPP_BASE_URL = 'https://wa.me'; diff --git a/packages/whatsapp/src/utils/helpers.ts b/packages/whatsapp/src/utils/helpers.ts new file mode 100644 index 000000000..29e49a905 --- /dev/null +++ b/packages/whatsapp/src/utils/helpers.ts @@ -0,0 +1,8 @@ +export const formatUrl = (url: string, params: { [key: string]: string }): string => { + const urlObject = new URL(url); + for (const [key, value] of Object.entries(params)) { + urlObject.searchParams.append(key, value); + } + + return urlObject.href; +}; diff --git a/packages/whatsapp/src/utils/selectors.ts b/packages/whatsapp/src/utils/selectors.ts new file mode 100644 index 000000000..e533dfb28 --- /dev/null +++ b/packages/whatsapp/src/utils/selectors.ts @@ -0,0 +1,9 @@ +import { generateSelectors, WHATSAPP_ATTRIBUTE } from '@finsweet/attributes-utils'; + +import { ELEMENTS, SETTINGS } from './constants'; + +export const { getAttribute, hasAttributeValue, queryAllElements, getInstanceIndex, queryElement } = generateSelectors( + WHATSAPP_ATTRIBUTE, + ELEMENTS, + SETTINGS +); diff --git a/packages/whatsapp/tsconfig.json b/packages/whatsapp/tsconfig.json new file mode 100644 index 000000000..4082f16a5 --- /dev/null +++ b/packages/whatsapp/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa5dff8a7..796619f09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -218,6 +218,9 @@ importers: '@finsweet/attributes-videohls': specifier: workspace:* version: link:../videohls + '@finsweet/attributes-whatsapp': + specifier: workspace:* + version: link:../whatsapp devDependencies: '@types/node': specifier: ^18.16.1 @@ -647,6 +650,12 @@ importers: specifier: ^1.4.5 version: 1.4.5 + packages/whatsapp: + dependencies: + '@finsweet/attributes-utils': + specifier: workspace:* + version: link:../utils + packages: /@babel/code-frame@7.21.4: From c21a863682ec1be3ee2222aa835d20c80317f276 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:45:03 +0300 Subject: [PATCH 02/13] feat (whatsapp): Format phone number --- packages/whatsapp/src/factory.ts | 8 ++++++-- packages/whatsapp/src/utils/helpers.ts | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/whatsapp/src/factory.ts b/packages/whatsapp/src/factory.ts index 859fb729a..1e6da4d0c 100644 --- a/packages/whatsapp/src/factory.ts +++ b/packages/whatsapp/src/factory.ts @@ -1,5 +1,5 @@ import { WHATSAPP_BASE_URL } from './utils/constants'; -import { formatUrl } from './utils/helpers'; +import { formatPhoneNumber, formatUrl } from './utils/helpers'; import { getAttribute, getInstanceIndex, queryElement } from './utils/selectors'; export const initWhatsappInstance = (buttonElement: Element) => { @@ -18,6 +18,10 @@ export const initWhatsappInstance = (buttonElement: Element) => { if (!phone || !message) return; //todo: throw new Error('Missing phone or message attribute'); - buttonElement.setAttribute('href', formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message })); + // format phone number and url + phone = formatPhoneNumber(phone); + const url = formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message }); + + buttonElement.setAttribute('href', url); buttonElement.setAttribute('target', '_blank'); }; diff --git a/packages/whatsapp/src/utils/helpers.ts b/packages/whatsapp/src/utils/helpers.ts index 29e49a905..d753e871e 100644 --- a/packages/whatsapp/src/utils/helpers.ts +++ b/packages/whatsapp/src/utils/helpers.ts @@ -6,3 +6,7 @@ export const formatUrl = (url: string, params: { [key: string]: string }): strin return urlObject.href; }; + +export const formatPhoneNumber = (phoneNumber: string): string => { + return phoneNumber.replace(/[^0-9+]/g, ''); +}; From a17dce48ac5f89ff815eb57f41f3f412f4b21123 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:52:58 +0300 Subject: [PATCH 03/13] feat (whatsapp): Wait for CMS to load before initializing --- packages/whatsapp/src/init.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/whatsapp/src/init.ts b/packages/whatsapp/src/init.ts index 8b2b5a852..a6dde82e1 100644 --- a/packages/whatsapp/src/init.ts +++ b/packages/whatsapp/src/init.ts @@ -1,4 +1,4 @@ -import { type FsAttributeInit, isNotEmpty, waitWebflowReady } from '@finsweet/attributes-utils'; +import { type FsAttributeInit, isNotEmpty, waitAttributeLoaded, waitWebflowReady } from '@finsweet/attributes-utils'; import { initWhatsappInstance } from './factory'; import { queryAllElements } from './utils/selectors'; @@ -9,6 +9,9 @@ import { queryAllElements } from './utils/selectors'; export const init: FsAttributeInit = async () => { await waitWebflowReady(); + // wait for cms to load if it's present + await waitAttributeLoaded('cmsload'); + // Get all whatsapp buttons on the page const buttonElements = queryAllElements('button'); From 61796479c6267253c6322ebc859bfcee514c380b Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 00:07:35 +0300 Subject: [PATCH 04/13] test (whatsapp): add attribute tests --- packages/attributes/tests/whatsapp.spec.ts | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/attributes/tests/whatsapp.spec.ts diff --git a/packages/attributes/tests/whatsapp.spec.ts b/packages/attributes/tests/whatsapp.spec.ts new file mode 100644 index 000000000..8fa31ace4 --- /dev/null +++ b/packages/attributes/tests/whatsapp.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://dev-attributes-whatsapp.webflow.io'); //todo: change to webflow dev url +}); + +test.describe('whatsapp', () => { + test('Validates whatsapp anchor tags', async ({ page }) => { + await page.waitForSelector('a[fs-whatsapp-element="button"]'); + + const whatsappButtons = await page.$$('a[fs-whatsapp-element="button"]'); + + // Loop through each WhatsApp button + for (const button of whatsappButtons) { + const ahref = await button.getAttribute('href'); + if (ahref === '#' || ahref === '') return; // Fail if href is '#' + + const href: URL = ahref as unknown as URL; // Get the 'href' attribute + + // convert href to a URL object + const url = new URL(href); + + const expectedPhoneNumber = await button.getAttribute('fs-whatsapp-phone'); // Get 'fs-whatsapp-phone' attribute + const expectedMessage = await button.getAttribute('fs-whatsapp-message'); // Get 'fs-whatsapp-message' attribute + + // Extract phone number and message from the URL + const urlSearchParams = url.searchParams; + const phoneNumber = url.pathname.split('/')[0]; + const message = urlSearchParams.get('text'); + + // Validate phone number and message using expect + expect(phoneNumber).toBe(expectedPhoneNumber); + expect(message).toBe(expectedMessage); + + // Validate nested elements + const phoneElement = await button.$('[fs-whatsapp-element="phone"]'); + const messageElement = await button.$('[fs-whatsapp-element="message"]'); + + if (phoneElement) { + expect(await phoneElement.textContent()).toBe(phoneNumber); + } + + if (messageElement) { + expect(await messageElement.textContent()).toBe(message); + } + } + }); +}); From 67a6da79bde6f511dbc42d7a9b92ea5cd7641568 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:13:15 +0300 Subject: [PATCH 05/13] test (whatsapp): improve tests --- packages/attributes/tests/whatsapp.spec.ts | 72 +++++++++++++--------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/attributes/tests/whatsapp.spec.ts b/packages/attributes/tests/whatsapp.spec.ts index 8fa31ace4..7e8e98932 100644 --- a/packages/attributes/tests/whatsapp.spec.ts +++ b/packages/attributes/tests/whatsapp.spec.ts @@ -1,47 +1,61 @@ -import { expect, test } from '@playwright/test'; +import { type ElementHandle, expect, test } from '@playwright/test'; + +import { waitAttributeLoaded } from './utils'; test.beforeEach(async ({ page }) => { - await page.goto('https://dev-attributes-whatsapp.webflow.io'); //todo: change to webflow dev url + await page.goto('https://dev-attributes-whatsapp.webflow.io'); }); -test.describe('whatsapp', () => { - test('Validates whatsapp anchor tags', async ({ page }) => { - await page.waitForSelector('a[fs-whatsapp-element="button"]'); +// reusable function to get common attributes and logs +async function getAttributesAndLogs(button: ElementHandle) { + const ahref = (await button.getAttribute('href')) ?? ''; + expect(ahref).toBeTruthy(); + expect(ahref).toContain('https://wa.me/'); + + const url = new URL(ahref); + const [, phoneNumber] = url.pathname.split('/'); + const message = url.searchParams.get('text'); - const whatsappButtons = await page.$$('a[fs-whatsapp-element="button"]'); + return { phoneNumber, message }; +} + +test.describe('whatsapp', () => { + test('Validates static attributes for whatsapp anchor tags', async ({ page }) => { + await waitAttributeLoaded(page, 'whatsapp'); + const whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle[]; - // Loop through each WhatsApp button for (const button of whatsappButtons) { - const ahref = await button.getAttribute('href'); - if (ahref === '#' || ahref === '') return; // Fail if href is '#' + const { phoneNumber, message } = await getAttributesAndLogs(button); - const href: URL = ahref as unknown as URL; // Get the 'href' attribute + const expectedPhoneNumber = await button.getAttribute('fs-whatsapp-phone'); + const expectedMessage = await button.getAttribute('fs-whatsapp-message'); - // convert href to a URL object - const url = new URL(href); + if (expectedPhoneNumber && expectedMessage) { + expect(phoneNumber).toBe(expectedPhoneNumber); + expect(message).toBe(expectedMessage); + } + } + }); - const expectedPhoneNumber = await button.getAttribute('fs-whatsapp-phone'); // Get 'fs-whatsapp-phone' attribute - const expectedMessage = await button.getAttribute('fs-whatsapp-message'); // Get 'fs-whatsapp-message' attribute + test('Validates dynamic attributes for whatsapp anchor tags', async ({ page }) => { + await waitAttributeLoaded(page, 'whatsapp'); + const whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle[]; - // Extract phone number and message from the URL - const urlSearchParams = url.searchParams; - const phoneNumber = url.pathname.split('/')[0]; - const message = urlSearchParams.get('text'); + for (const button of whatsappButtons) { + const { phoneNumber, message } = await getAttributesAndLogs(button); - // Validate phone number and message using expect - expect(phoneNumber).toBe(expectedPhoneNumber); - expect(message).toBe(expectedMessage); + const phoneElement: ElementHandle | null = await button.$('div[fs-whatsapp-element="phone"]'); + const messageElement: ElementHandle | null = await button.$('div[fs-whatsapp-element="message"]'); - // Validate nested elements - const phoneElement = await button.$('[fs-whatsapp-element="phone"]'); - const messageElement = await button.$('[fs-whatsapp-element="message"]'); + if (phoneElement && messageElement) { + const dynamicMessage = (await messageElement.textContent()) ?? ''; + expect(dynamicMessage).toBeTruthy(); - if (phoneElement) { - expect(await phoneElement.textContent()).toBe(phoneNumber); - } + const dynamicPhone = (await phoneElement.textContent()) ?? ''; + expect(dynamicPhone).toBeTruthy(); - if (messageElement) { - expect(await messageElement.textContent()).toBe(message); + expect(dynamicPhone).toBe(phoneNumber); + expect(dynamicMessage).toBe(message); } } }); From b8334309cc80b0a61ed43bc3c8031fbb6b25c793 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:08:49 +0300 Subject: [PATCH 06/13] test (whatsapp): refactor & reformat code --- packages/attributes/tests/whatsapp.spec.ts | 58 ++++++++++++++-------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/attributes/tests/whatsapp.spec.ts b/packages/attributes/tests/whatsapp.spec.ts index 7e8e98932..2c1c17fa1 100644 --- a/packages/attributes/tests/whatsapp.spec.ts +++ b/packages/attributes/tests/whatsapp.spec.ts @@ -2,34 +2,28 @@ import { type ElementHandle, expect, test } from '@playwright/test'; import { waitAttributeLoaded } from './utils'; +let whatsappButtons: ElementHandle[] = []; + test.beforeEach(async ({ page }) => { await page.goto('https://dev-attributes-whatsapp.webflow.io'); -}); - -// reusable function to get common attributes and logs -async function getAttributesAndLogs(button: ElementHandle) { - const ahref = (await button.getAttribute('href')) ?? ''; - expect(ahref).toBeTruthy(); - expect(ahref).toContain('https://wa.me/'); + await waitAttributeLoaded(page, 'whatsapp'); - const url = new URL(ahref); - const [, phoneNumber] = url.pathname.split('/'); - const message = url.searchParams.get('text'); - - return { phoneNumber, message }; -} + // Get all whatsapp buttons + whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle[]; +}); test.describe('whatsapp', () => { - test('Validates static attributes for whatsapp anchor tags', async ({ page }) => { - await waitAttributeLoaded(page, 'whatsapp'); - const whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle[]; + // Static attributes test + test('Validates static attributes for whatsapp anchor tags', async () => { + // Loop through all whatsapp buttons and validate the attributes for (const button of whatsappButtons) { - const { phoneNumber, message } = await getAttributesAndLogs(button); + const { phoneNumber, message } = await getAttributes(button); const expectedPhoneNumber = await button.getAttribute('fs-whatsapp-phone'); const expectedMessage = await button.getAttribute('fs-whatsapp-message'); + // Assert that the attributes are not empty if (expectedPhoneNumber && expectedMessage) { expect(phoneNumber).toBe(expectedPhoneNumber); expect(message).toBe(expectedMessage); @@ -37,16 +31,17 @@ test.describe('whatsapp', () => { } }); - test('Validates dynamic attributes for whatsapp anchor tags', async ({ page }) => { - await waitAttributeLoaded(page, 'whatsapp'); - const whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle[]; - + // Dynamic attributes test + test('Validates dynamic attributes for whatsapp anchor tags', async () => { + // Loop through all whatsapp buttons and validate the attributes for (const button of whatsappButtons) { - const { phoneNumber, message } = await getAttributesAndLogs(button); + // Get the attributes from the whatsapp button + const { phoneNumber, message } = await getAttributes(button); const phoneElement: ElementHandle | null = await button.$('div[fs-whatsapp-element="phone"]'); const messageElement: ElementHandle | null = await button.$('div[fs-whatsapp-element="message"]'); + // Assert that the attributes are not empty if (phoneElement && messageElement) { const dynamicMessage = (await messageElement.textContent()) ?? ''; expect(dynamicMessage).toBeTruthy(); @@ -60,3 +55,22 @@ test.describe('whatsapp', () => { } }); }); + +/** + * Helper function that gets the attributes from a whatsapp button + * @param button The whatsapp button + * @returns An object containing the phone number and message + */ +async function getAttributes(button: ElementHandle) { + const href = (await button.getAttribute('href')) ?? ''; + expect(href).toBeTruthy(); + + // assert that the href value is a valid whatsapp link + expect(href).toContain('https://wa.me/'); + + const url = new URL(href); + const [, phoneNumber] = url.pathname.split('/'); + const message = url.searchParams.get('text'); + + return { phoneNumber, message }; +} From abb6a54489a281256d102e3db10dd9c463506ebb Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:10:46 +0300 Subject: [PATCH 07/13] test (whatsapp): skip tests on webkit, for now! --- packages/attributes/tests/whatsapp.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/attributes/tests/whatsapp.spec.ts b/packages/attributes/tests/whatsapp.spec.ts index 2c1c17fa1..6f3f07d13 100644 --- a/packages/attributes/tests/whatsapp.spec.ts +++ b/packages/attributes/tests/whatsapp.spec.ts @@ -13,6 +13,8 @@ test.beforeEach(async ({ page }) => { }); test.describe('whatsapp', () => { + // Skip this test on webkit because it doesn't work + test.fixme(({ browserName }) => browserName === 'webkit'); // Static attributes test test('Validates static attributes for whatsapp anchor tags', async () => { From ef7322dd30dfdc88d2580574dddb04245d76c71f Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:12:02 +0300 Subject: [PATCH 08/13] Revert "test (whatsapp): skip tests on webkit, for now!" This reverts commit abb6a54489a281256d102e3db10dd9c463506ebb. --- packages/attributes/tests/whatsapp.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/attributes/tests/whatsapp.spec.ts b/packages/attributes/tests/whatsapp.spec.ts index 6f3f07d13..2c1c17fa1 100644 --- a/packages/attributes/tests/whatsapp.spec.ts +++ b/packages/attributes/tests/whatsapp.spec.ts @@ -13,8 +13,6 @@ test.beforeEach(async ({ page }) => { }); test.describe('whatsapp', () => { - // Skip this test on webkit because it doesn't work - test.fixme(({ browserName }) => browserName === 'webkit'); // Static attributes test test('Validates static attributes for whatsapp anchor tags', async () => { From 588fecdf6c950c6c5bba81f0a0260d47ae72c66c Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:00:17 +0300 Subject: [PATCH 09/13] docs (whatsapp): Write missing jsdocs --- packages/whatsapp/src/factory.ts | 5 +++++ packages/whatsapp/src/utils/helpers.ts | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/whatsapp/src/factory.ts b/packages/whatsapp/src/factory.ts index 1e6da4d0c..aaa73d55c 100644 --- a/packages/whatsapp/src/factory.ts +++ b/packages/whatsapp/src/factory.ts @@ -2,6 +2,11 @@ import { WHATSAPP_BASE_URL } from './utils/constants'; import { formatPhoneNumber, formatUrl } from './utils/helpers'; import { getAttribute, getInstanceIndex, queryElement } from './utils/selectors'; +/** + * Initialize a WhatsApp button element instance + * @param buttonElement The button element to initialize + * @returns The initialized button element + */ export const initWhatsappInstance = (buttonElement: Element) => { // static let phone = getAttribute(buttonElement, 'phone'); diff --git a/packages/whatsapp/src/utils/helpers.ts b/packages/whatsapp/src/utils/helpers.ts index d753e871e..c4f441993 100644 --- a/packages/whatsapp/src/utils/helpers.ts +++ b/packages/whatsapp/src/utils/helpers.ts @@ -1,3 +1,9 @@ +/** + * Formats a url with the given params as query params + * @param url The url to format + * @param params The params to append to the url + * @returns The formatted url + */ export const formatUrl = (url: string, params: { [key: string]: string }): string => { const urlObject = new URL(url); for (const [key, value] of Object.entries(params)) { @@ -7,6 +13,11 @@ export const formatUrl = (url: string, params: { [key: string]: string }): strin return urlObject.href; }; +/** + * Formats a phone number by removing all non-numeric characters + * @param phoneNumber The phone number to format + * @returns The formatted phone number + */ export const formatPhoneNumber = (phoneNumber: string): string => { return phoneNumber.replace(/[^0-9+]/g, ''); }; From 184d3b9f307a7ac4545dfb7efe7336250b8f5194 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:03:09 +0300 Subject: [PATCH 10/13] refactor: code cleanup and reformat --- packages/attributes/tests/whatsapp.spec.ts | 1 - packages/whatsapp/api/examples.ts | 5 --- packages/whatsapp/api/schema.ts | 8 ---- packages/whatsapp/src/utils/schema.ts | 43 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 14 deletions(-) delete mode 100644 packages/whatsapp/api/examples.ts delete mode 100644 packages/whatsapp/api/schema.ts create mode 100644 packages/whatsapp/src/utils/schema.ts diff --git a/packages/attributes/tests/whatsapp.spec.ts b/packages/attributes/tests/whatsapp.spec.ts index 2c1c17fa1..e867fdd7f 100644 --- a/packages/attributes/tests/whatsapp.spec.ts +++ b/packages/attributes/tests/whatsapp.spec.ts @@ -13,7 +13,6 @@ test.beforeEach(async ({ page }) => { }); test.describe('whatsapp', () => { - // Static attributes test test('Validates static attributes for whatsapp anchor tags', async () => { // Loop through all whatsapp buttons and validate the attributes diff --git a/packages/whatsapp/api/examples.ts b/packages/whatsapp/api/examples.ts deleted file mode 100644 index ce6cd2742..000000000 --- a/packages/whatsapp/api/examples.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { AttributeExamples } from '@finsweet/attributes-utils'; - -const examples: AttributeExamples = []; - -export default examples; diff --git a/packages/whatsapp/api/schema.ts b/packages/whatsapp/api/schema.ts deleted file mode 100644 index 3b22a2860..000000000 --- a/packages/whatsapp/api/schema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { AttributeSchema } from '@finsweet/attributes-utils'; - -const schema: AttributeSchema = { - elements: [], - settings: [], -}; - -export default schema; diff --git a/packages/whatsapp/src/utils/schema.ts b/packages/whatsapp/src/utils/schema.ts new file mode 100644 index 000000000..6a1fb3914 --- /dev/null +++ b/packages/whatsapp/src/utils/schema.ts @@ -0,0 +1,43 @@ +import type { Schema, SchemaSettings } from '@finsweet/attributes-utils'; + +import { ELEMENTS, SETTINGS } from './constants'; + +const SCHEMA_SETTINGS: SchemaSettings = { + phone: { + ...SETTINGS.phone, + name: 'Phone', + description: 'The phone number', + type: 'text', + }, + message: { + ...SETTINGS.message, + name: 'Message', + description: 'The message to send', + type: 'text', + }, +}; + +export const schema: Schema = { + groups: [], + elements: [ + { + key: 'button', + name: 'Button', + description: 'A button to open WhatsApp', + allowedTypes: ['Link'], + settings: [SCHEMA_SETTINGS.phone, SCHEMA_SETTINGS.message], + }, + { + key: 'phone', + name: 'Phone', + description: 'A phone number to open WhatsApp', + allowedTypes: ['Block'], + }, + { + key: 'message', + name: 'Message', + description: 'A message to open WhatsApp', + allowedTypes: ['Block'], + }, + ], +}; From 808496a0c264a39f4389d09cd907fe5cc1e470f2 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:27:38 +0300 Subject: [PATCH 11/13] fix: possible issue with the regex If user accidentally adds a number like ++1233434, this regex will not remove the extra plus. Therefore, remove everything that is not a number from the phone number, then append a '+' sign --- packages/whatsapp/src/utils/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/whatsapp/src/utils/helpers.ts b/packages/whatsapp/src/utils/helpers.ts index c4f441993..6a1b9a3e3 100644 --- a/packages/whatsapp/src/utils/helpers.ts +++ b/packages/whatsapp/src/utils/helpers.ts @@ -14,10 +14,10 @@ export const formatUrl = (url: string, params: { [key: string]: string }): strin }; /** - * Formats a phone number by removing all non-numeric characters + * Formats a phone number by removing all non-numeric characters and appending + * @param phoneNumber The phone number to format * @returns The formatted phone number */ export const formatPhoneNumber = (phoneNumber: string): string => { - return phoneNumber.replace(/[^0-9+]/g, ''); + return '+' + phoneNumber.replace(/[^0-9]/g, ''); }; From 092e4a3ae705627b3ce8dd1057e5fae1eddd5397 Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Wed, 30 Aug 2023 02:22:13 +0300 Subject: [PATCH 12/13] chore: handle all-in-one named exports --- packages/whatsapp/src/factory.ts | 4 +--- packages/whatsapp/src/index.ts | 2 +- packages/whatsapp/src/init.ts | 2 +- packages/whatsapp/src/utils/index.ts | 4 ++++ packages/whatsapp/src/utils/schema.ts | 2 +- packages/whatsapp/src/utils/selectors.ts | 2 +- pnpm-lock.yaml | 23 ----------------------- 7 files changed, 9 insertions(+), 30 deletions(-) create mode 100644 packages/whatsapp/src/utils/index.ts diff --git a/packages/whatsapp/src/factory.ts b/packages/whatsapp/src/factory.ts index aaa73d55c..e8b4ca849 100644 --- a/packages/whatsapp/src/factory.ts +++ b/packages/whatsapp/src/factory.ts @@ -1,6 +1,4 @@ -import { WHATSAPP_BASE_URL } from './utils/constants'; -import { formatPhoneNumber, formatUrl } from './utils/helpers'; -import { getAttribute, getInstanceIndex, queryElement } from './utils/selectors'; +import { formatPhoneNumber, formatUrl, getAttribute, getInstanceIndex, queryElement, WHATSAPP_BASE_URL } from './utils'; /** * Initialize a WhatsApp button element instance diff --git a/packages/whatsapp/src/index.ts b/packages/whatsapp/src/index.ts index 76864f6e2..f9f07f0e7 100644 --- a/packages/whatsapp/src/index.ts +++ b/packages/whatsapp/src/index.ts @@ -1,3 +1,3 @@ export { version } from '../package.json'; export { init } from './init'; -export { ELEMENTS, SETTINGS } from './utils/constants'; +export { ELEMENTS, SETTINGS } from './utils'; diff --git a/packages/whatsapp/src/init.ts b/packages/whatsapp/src/init.ts index a6dde82e1..bcb06f62f 100644 --- a/packages/whatsapp/src/init.ts +++ b/packages/whatsapp/src/init.ts @@ -1,7 +1,7 @@ import { type FsAttributeInit, isNotEmpty, waitAttributeLoaded, waitWebflowReady } from '@finsweet/attributes-utils'; import { initWhatsappInstance } from './factory'; -import { queryAllElements } from './utils/selectors'; +import { queryAllElements } from './utils'; /** * Inits the attribute. diff --git a/packages/whatsapp/src/utils/index.ts b/packages/whatsapp/src/utils/index.ts new file mode 100644 index 000000000..f36792e8d --- /dev/null +++ b/packages/whatsapp/src/utils/index.ts @@ -0,0 +1,4 @@ +export * from './constants'; +export * from './helpers'; +export * from './schema'; +export * from './selectors'; diff --git a/packages/whatsapp/src/utils/schema.ts b/packages/whatsapp/src/utils/schema.ts index 6a1fb3914..8d49e18b3 100644 --- a/packages/whatsapp/src/utils/schema.ts +++ b/packages/whatsapp/src/utils/schema.ts @@ -1,6 +1,6 @@ import type { Schema, SchemaSettings } from '@finsweet/attributes-utils'; -import { ELEMENTS, SETTINGS } from './constants'; +import { ELEMENTS, SETTINGS } from '.'; const SCHEMA_SETTINGS: SchemaSettings = { phone: { diff --git a/packages/whatsapp/src/utils/selectors.ts b/packages/whatsapp/src/utils/selectors.ts index e533dfb28..673e7b1f2 100644 --- a/packages/whatsapp/src/utils/selectors.ts +++ b/packages/whatsapp/src/utils/selectors.ts @@ -1,6 +1,6 @@ import { generateSelectors, WHATSAPP_ATTRIBUTE } from '@finsweet/attributes-utils'; -import { ELEMENTS, SETTINGS } from './constants'; +import { ELEMENTS, SETTINGS } from '.'; export const { getAttribute, hasAttributeValue, queryAllElements, getInstanceIndex, queryElement } = generateSelectors( WHATSAPP_ATTRIBUTE, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 796619f09..875e3326a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -416,19 +416,6 @@ importers: specifier: workspace:* version: link:../utils - packages/docs: - dependencies: - '@finsweet/attributes-utils': - specifier: workspace:* - version: link:../utils - marked: - specifier: ^5.1.0 - version: 5.1.0 - devDependencies: - '@types/marked': - specifier: ^5.0.0 - version: 5.0.0 - packages/favcustom: dependencies: '@finsweet/attributes-utils': @@ -1505,10 +1492,6 @@ packages: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true - /@types/marked@5.0.0: - resolution: {integrity: sha512-YcZe50jhltsCq7rc9MNZC/4QB/OnA2Pd6hrOSTOFajtabN+38slqgDDCeE/0F83SjkKBQcsZUj7VLWR0H5cKRA==} - dev: true - /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true @@ -3018,12 +3001,6 @@ packages: engines: {node: '>=8'} dev: true - /marked@5.1.0: - resolution: {integrity: sha512-z3/nBe7aTI8JDszlYLk7dDVNpngjw0o1ZJtrA9kIfkkHcIF+xH7mO23aISl4WxP83elU+MFROgahqdpd05lMEQ==} - engines: {node: '>= 18'} - hasBin: true - dev: false - /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} From 717ff703126969db2629a9c5e67d7863f824897a Mon Sep 17 00:00:00 2001 From: Michael Gatuma <50529359+michaelgatuma@users.noreply.github.com> Date: Thu, 26 Oct 2023 01:35:07 +0300 Subject: [PATCH 13/13] fix: trim wrapping spaces from message text --- packages/whatsapp/src/factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/whatsapp/src/factory.ts b/packages/whatsapp/src/factory.ts index e8b4ca849..afbe4f63e 100644 --- a/packages/whatsapp/src/factory.ts +++ b/packages/whatsapp/src/factory.ts @@ -23,7 +23,7 @@ export const initWhatsappInstance = (buttonElement: Element) => { // format phone number and url phone = formatPhoneNumber(phone); - const url = formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message }); + const url = formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message.trim() }); buttonElement.setAttribute('href', url); buttonElement.setAttribute('target', '_blank');