Skip to content

Commit

Permalink
adds prop update listening to modal browser zoid polyfill
Browse files Browse the repository at this point in the history
  • Loading branch information
danzhaaspaypal committed Dec 4, 2024
1 parent 713635e commit 48ed5aa
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 78 deletions.
5 changes: 3 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": ["airbnb", "prettier"],
"extends": ["airbnb", "prettier", "plugin:import/typescript"],
"parser": "@babel/eslint-parser",
"env": {
"browser": true,
Expand Down Expand Up @@ -39,7 +39,8 @@
"server": "./server",
"utils": "./utils"
}
}
},
"typescript": {}
}
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@paypal/sdk-client": "^4.0.166",
"@paypal/sdk-constants": "^1.0.118",
"@paypal/sdk-logos": "^2.0.0",
"@paypalcorp/web-sdk-postmessenger": "^0.2.6",
"core-js-pure": "3.31.1"
},
"devDependencies": {
Expand Down Expand Up @@ -81,6 +82,7 @@
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-babel-module": "^5.3.1",
"eslint-import-resolver-jest": "^3.0.2",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.2.1",
Expand Down
33 changes: 31 additions & 2 deletions src/components/modal/v2/lib/zoid-polyfill.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
/* global Android */
import { isAndroidWebview, isIosWebview, getPerformance } from '@krakenjs/belter/src';
import PostMessenger from '@paypalcorp/web-sdk-postmessenger/src';
import { getOrCreateDeviceID, logger } from '../../../../utils';
import { isIframe } from './utils';

const IOS_INTERFACE_NAME = 'paypalMessageModalCallbackHandler';
const ANDROID_INTERFACE_NAME = 'paypalMessageModalCallbackHandler';

const channelConfigObj = {
// where to send or recieve the messages
target: window, // TODO: determine window or window.top?
// restricted list of destinations to send or recieve from
allowedOrigins: [], // string[]
// dump debug information
debug: console.log.bind(console), // just a simple place to log info,
// time to wait for message ack
timeout: 2000,
// number of times to retry failed messages
retries: 3
};

const setupBrowser = props => {
// setup communication layer with v6 modal wrapper
const channel = new PostMessenger(channelConfigObj);

const propListeners = new Set();

channel.subscribe('PROPS_UPDATE', newProps => {
if (newProps && typeof newProps === 'object') {
Array.from(propListeners.values()).forEach(listener => {
listener({ ...window.xprops, ...newProps });
});

Object.assign(window.xprops, newProps);
}
console.log('Reciever got PROPS_UPDATE data =', newProps);
});

window.xprops = {
// We will never recieve new props via this integration style
onProps: () => {},
onProps: listener => propListeners.add(listener),
// TODO: Verify these callbacks are instrumented correctly
onReady: ({ products, meta }) => {
const { clientId, payerId, merchantId, offer, partnerAttributionId } = props;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { currencyFormat } from 'src/components/modal/v2/lib/';
import { currencyFormat } from 'src/components/modal/v2/lib/hooks/currency';

describe('currency format', () => {
test('array test', () => {
Expand Down
230 changes: 157 additions & 73 deletions tests/unit/spec/src/components/modal/v2/lib/zoid-polyfill.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { subscribeMockFn } from '@paypalcorp/web-sdk-postmessenger/src';
import zoidPolyfill from 'src/components/modal/v2/lib/zoid-polyfill';
import { logger } from 'src/utils';

Expand Down Expand Up @@ -25,6 +26,16 @@ jest.mock('@krakenjs/belter/src', () => {
})
};
});
jest.mock('@paypalcorp/web-sdk-postmessenger/src', () => {
const subscribeMock = jest.fn();
return {
__esModule: true,
subscribeMockFn: subscribeMock,
default: jest.fn().mockImplementation(() => ({
subscribe: subscribeMock
}))
};
});
jest.mock('src/components/modal/v2/lib/utils', () => ({
isIframe: true
}));
Expand Down Expand Up @@ -73,7 +84,7 @@ describe('zoidPollyfill', () => {
describe('sets up xprops for browser', () => {
beforeAll(() => {
mockLoadUrl(
'https://localhost.paypal.com:8080/credit-presentment/native/message?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true'
'https://localhost.paypal.com:8080/credit-presentment/lander/modal?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true'
);

zoidPolyfill();
Expand Down Expand Up @@ -177,7 +188,7 @@ describe('zoidPollyfill', () => {

test('sets up xprops for webview', () => {
mockLoadUrl(
'https://localhost.paypal.com:8080/credit-presentment/native/message?client_id=client_1&logo_type=inline&amount=500&dev_touchpoint=true',
'https://localhost.paypal.com:8080/credit-presentment/native/modal?client_id=client_1&logo_type=inline&amount=500&dev_touchpoint=true',
{
platform: 'ios'
}
Expand Down Expand Up @@ -316,94 +327,167 @@ describe('zoidPollyfill', () => {
postMessage.mockClear();
});

test('notifies when props update', () => {
mockLoadUrl(
'https://localhost.paypal.com:8080/credit-presentment/native/message?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true',
{
platform: 'android'
}
);
const postMessage = global.Android.paypalMessageModalCallbackHandler;
describe('notifies when props update', () => {
afterEach(() => {
subscribeMockFn.mockClear();
});
test('webview', () => {
mockLoadUrl(
'https://localhost.paypal.com:8080/credit-presentment/native/modal?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true',
{
platform: 'android'
}
);
const postMessage = global.Android.paypalMessageModalCallbackHandler;

zoidPolyfill();
zoidPolyfill();

expect(window.actions).toEqual(
expect.objectContaining({
updateProps: expect.any(Function)
})
);
expect(window.xprops).toEqual(
expect.objectContaining({
onProps: expect.any(Function)
})
);
expect(window.actions).toEqual(
expect.objectContaining({
updateProps: expect.any(Function)
})
);
expect(window.xprops).toEqual(
expect.objectContaining({
onProps: expect.any(Function)
})
);

const onPropsCallback = jest.fn();
const onPropsCallback = jest.fn();

window.xprops.onProps(onPropsCallback);
window.actions.updateProps({ amount: 1000 });
window.xprops.onProps(onPropsCallback);
window.actions.updateProps({ amount: 1000 });

expect(onPropsCallback).toHaveBeenCalledTimes(1);
expect(onPropsCallback).toHaveBeenCalledWith(
expect.objectContaining({
clientId: 'client_1',
logoType: 'inline',
amount: 1000
})
);
expect(onPropsCallback).toHaveBeenCalledTimes(1);
expect(onPropsCallback).toHaveBeenCalledWith(
expect.objectContaining({
clientId: 'client_1',
logoType: 'inline',
amount: 1000
})
);

window.actions.updateProps({ offer: 'TEST' });
window.actions.updateProps({ offer: 'TEST' });

expect(onPropsCallback).toHaveBeenCalledTimes(2);
expect(onPropsCallback).toHaveBeenCalledWith(
expect.objectContaining({
clientId: 'client_1',
logoType: 'inline',
amount: 1000,
offer: 'TEST'
})
);
expect(onPropsCallback).toHaveBeenCalledTimes(2);
expect(onPropsCallback).toHaveBeenCalledWith(
expect.objectContaining({
clientId: 'client_1',
logoType: 'inline',
amount: 1000,
offer: 'TEST'
})
);

window.xprops.onReady({
products: ['PRODUCT_1', 'PRODUCT_2'],
meta: {
trackingDetails: {
fdata: '123abc',
credit_product_identifiers: ['PAY_LATER_LONG_TERM_US'],
offer_country_code: 'US',
extra_field: 'should not be present'
window.xprops.onReady({
products: ['PRODUCT_1', 'PRODUCT_2'],
meta: {
trackingDetails: {
fdata: '123abc',
credit_product_identifiers: ['PAY_LATER_LONG_TERM_US'],
offer_country_code: 'US',
extra_field: 'should not be present'
}
}
}
});
});

expect(postMessage).toHaveBeenCalledTimes(1);
expect(postMessage.mock.calls[0][0]).toEqual(expect.any(String));
expect(JSON.parse(postMessage.mock.calls[0][0])).toMatchInlineSnapshot(`
Object {
"args": Array [
expect(postMessage).toHaveBeenCalledTimes(1);
expect(postMessage.mock.calls[0][0]).toEqual(expect.any(String));
expect(JSON.parse(postMessage.mock.calls[0][0])).toMatchInlineSnapshot(`
Object {
"__shared__": Object {
"credit_product_identifiers": Array [
"PAY_LATER_LONG_TERM_US",
],
"fdata": "123abc",
"offer_country_code": "US",
},
"event_type": "modal_rendered",
"render_duration": "50",
"request_duration": "100",
"args": Array [
Object {
"__shared__": Object {
"credit_product_identifiers": Array [
"PAY_LATER_LONG_TERM_US",
],
"fdata": "123abc",
"offer_country_code": "US",
},
"event_type": "modal_rendered",
"render_duration": "50",
"request_duration": "100",
},
],
"name": "onReady",
}
`);
postMessage.mockClear();
});
test('browser', () => {
mockLoadUrl(
'https://localhost.paypal.com:8080/credit-presentment/lander/modal?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true'
);

zoidPolyfill();

expect(window.xprops).toEqual(
expect.objectContaining({
onProps: expect.any(Function)
})
);

const onPropsCallback = jest.fn();

window.xprops.onProps(onPropsCallback);

expect(subscribeMockFn).toHaveBeenCalledTimes(1);
expect(subscribeMockFn).toHaveBeenCalledWith('PROPS_UPDATE', expect.any(Function));

subscribeMockFn(
{
amount: 1000
},
],
"name": "onReady",
}
`);
postMessage.mockClear();
window.origin
);

const subscribeCallback = subscribeMockFn.mock.calls[0][1];

subscribeCallback({
amount: 1000
});

expect(onPropsCallback).toHaveBeenCalledTimes(1);
expect(onPropsCallback).toHaveBeenCalledWith(
expect.objectContaining({
clientId: 'client_1',
logoType: 'inline',
amount: 1000
})
);

subscribeCallback({
offerTypes: ['TEST']
});

expect(onPropsCallback).toHaveBeenCalledTimes(2);
expect(onPropsCallback).toHaveBeenCalledWith(
expect.objectContaining({
clientId: 'client_1',
logoType: 'inline',
amount: 1000,
offerTypes: ['TEST']
})
);

window.xprops.onReady({
products: ['PRODUCT_1', 'PRODUCT_2'],
meta: {
trackingDetails: {
fdata: '123abc',
credit_product_identifiers: ['PAY_LATER_LONG_TERM_US'],
offer_country_code: 'US',
extra_field: 'should not be present'
}
}
});
});
});

describe('communication with parent window on onClose ', () => {
beforeAll(() => {
mockLoadUrl(
'https://localhost.paypal.com:8080/credit-presentment/native/message?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true'
'https://localhost.paypal.com:8080/credit-presentment/native/modal?client_id=client_1&logo_type=inline&amount=500&devTouchpoint=true'
);
zoidPolyfill();
const postMessage = jest.fn();
Expand Down

0 comments on commit 48ed5aa

Please sign in to comment.