From 3d4b21590d0b8600eabdda92a7e62c7982cc22b5 Mon Sep 17 00:00:00 2001 From: skyflow-sapanreddy Date: Wed, 15 Nov 2023 16:55:40 +0530 Subject: [PATCH 01/32] SK-1233 config embedded in URL for reveal-iframes --- src/core/external/common/iframe.ts | 4 ++-- src/core/external/reveal/reveal-element.ts | 8 +++++++- src/core/internal/reveal/reveal-frame.ts | 8 +++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/core/external/common/iframe.ts b/src/core/external/common/iframe.ts index ff8b2017..b11844ed 100644 --- a/src/core/external/common/iframe.ts +++ b/src/core/external/common/iframe.ts @@ -27,7 +27,7 @@ export default class IFrame { setAttributes(this.iframe, { src: responseValue }); }; - mount = (domElement) => { + mount = (domElement, data?:any) => { this.unmount(); try { if (typeof domElement === 'string') { @@ -46,7 +46,7 @@ export default class IFrame { // throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ELEMENT_SELECTOR, [], true); } - setAttributes(this.iframe, { src: getIframeSrc() }); + setAttributes(this.iframe, { src: `${getIframeSrc()}?${btoa(data?.record)}` }); this.container?.appendChild(this.iframe); }; diff --git a/src/core/external/reveal/reveal-element.ts b/src/core/external/reveal/reveal-element.ts index 140a29d6..babbf39a 100644 --- a/src/core/external/reveal/reveal-element.ts +++ b/src/core/external/reveal/reveal-element.ts @@ -135,7 +135,13 @@ class RevealElement extends SkyflowElement { } this.#eventEmitter?.on(ELEMENT_EVENTS_TO_CONTAINER.REVEAL_CONTAINER_MOUNTED, (data) => { if (data?.containerId === this.#containerId) { - this.#iframe.mount(domElementSelector); + this.#iframe.mount(domElementSelector, { + record: JSON.stringify({ + ...this.#metaData, + record: this.#recordData, + context: this.#context, + }), + }); bus .target(properties.IFRAME_SECURE_ORGIN) .on(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY, sub); diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 622a9c7a..f404346a 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -61,10 +61,12 @@ class RevealFrame { .emit( ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY, { name: window.name }, - (data: any) => { - RevealFrame.revealFrame = new RevealFrame(data.record, data.context); - }, + () => {}, ); + const url = window.location.href.split('?')[1]; + const ed = decodeURIComponent(url); + const edt = JSON.parse(atob(ed)); + RevealFrame.revealFrame = new RevealFrame(edt.record, edt.context); } constructor(record, context) { From eaa9eb0cc561d1687ecc34cd062815b588c54e2a Mon Sep 17 00:00:00 2001 From: skyflow-sapanreddy Date: Mon, 20 Nov 2023 16:50:23 +0530 Subject: [PATCH 02/32] SK-1233 config embedded URL for collect-containers --- src/core/external/collect/collect-element.ts | 12 +++++++++--- src/core/external/common/iframe.ts | 2 +- src/core/external/reveal/reveal-element.ts | 8 +++++++- src/core/internal/frame-elements.ts | 17 ++++++++++------- src/core/internal/iframe-form/index.ts | 2 +- src/core/internal/reveal/reveal-frame.ts | 6 +++--- 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 904f00c8..46a346dd 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -196,17 +196,23 @@ class CollectElement extends SkyflowElement { const isComposable = this.#elements.length > 1; if (isComposable) { - this.#iframe.mount(domElement); + this.#iframe.mount(domElement, { + record: JSON.stringify({ record: this.#group }), + }); this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + this.containerId, sub); } else { if (this.#readyToMount) { - this.#iframe.mount(domElement); + this.#iframe.mount(domElement, { + record: JSON.stringify({ record: this.#group }), + }); this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + this.containerId, sub); return; } this.#groupEmitter?.on(ELEMENT_EVENTS_TO_CONTAINER.COLLECT_CONTAINER_MOUNTED, (data) => { if (data?.containerId === this.containerId) { - this.#iframe.mount(domElement); + this.#iframe.mount(domElement, { + record: JSON.stringify({ record: this.#group }), + }); this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + this.containerId, sub); } }); diff --git a/src/core/external/common/iframe.ts b/src/core/external/common/iframe.ts index b11844ed..ce04140f 100644 --- a/src/core/external/common/iframe.ts +++ b/src/core/external/common/iframe.ts @@ -46,7 +46,7 @@ export default class IFrame { // throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ELEMENT_SELECTOR, [], true); } - setAttributes(this.iframe, { src: `${getIframeSrc()}?${btoa(data?.record)}` }); + setAttributes(this.iframe, { src: `${getIframeSrc()}${data ? `?${btoa(data?.record)}` : ''}` }); this.container?.appendChild(this.iframe); }; diff --git a/src/core/external/reveal/reveal-element.ts b/src/core/external/reveal/reveal-element.ts index babbf39a..9821f50d 100644 --- a/src/core/external/reveal/reveal-element.ts +++ b/src/core/external/reveal/reveal-element.ts @@ -127,7 +127,13 @@ class RevealElement extends SkyflowElement { }; if (this.#readyToMount) { - this.#iframe.mount(domElementSelector); + this.#iframe.mount(domElementSelector, { + record: JSON.stringify({ + ...this.#metaData, + record: this.#recordData, + context: this.#context, + }), + }); bus .target(properties.IFRAME_SECURE_ORGIN) .on(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY, sub); diff --git a/src/core/internal/frame-elements.ts b/src/core/internal/frame-elements.ts index cf601ef3..fe22467a 100644 --- a/src/core/internal/frame-elements.ts +++ b/src/core/internal/frame-elements.ts @@ -63,18 +63,21 @@ export default class FrameElements { bus.emit( ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + names[3], { name: window.name }, - (group: any) => { + () => { printLog(parameterizedString(logs.infoLogs.COLLECT_FRAME_READY_CB, CLASS_NAME, getElementName(window.name)), MessageType.LOG, logLevel); - FrameElements.group = group; - if (FrameElements.frameElements) { - printLog(parameterizedString(logs.infoLogs.SETUP_IN_START, CLASS_NAME), - MessageType.LOG, logLevel); - FrameElements.frameElements.setup(); // start the process - } }, ); + const url = window.location.href.split('?')[1]; + const encodedString = decodeURIComponent(url); + const parsedRecord = JSON.parse(atob(encodedString)); + FrameElements.group = parsedRecord.record; + if (FrameElements.frameElements) { + printLog(parameterizedString(logs.infoLogs.SETUP_IN_START, CLASS_NAME), + MessageType.LOG, logLevel); + FrameElements.frameElements.setup(); // start the process + } }; // called by IFrameForm diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index f4a94a9c..8e30d84e 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -1151,7 +1151,7 @@ export class IFrameForm { try { if ( - frame.location.href === window.location.href + frame.location.href.split('?')[0] === window.location.href && frame.name === frameGlobalName ) { frameInstance = frame; diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index f404346a..39718a27 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -64,9 +64,9 @@ class RevealFrame { () => {}, ); const url = window.location.href.split('?')[1]; - const ed = decodeURIComponent(url); - const edt = JSON.parse(atob(ed)); - RevealFrame.revealFrame = new RevealFrame(edt.record, edt.context); + const encodedString = decodeURIComponent(url); + const parsedRecord = JSON.parse(atob(encodedString)); + RevealFrame.revealFrame = new RevealFrame(parsedRecord.record, parsedRecord.context); } constructor(record, context) { From 208b11845c37a363b800a1da5d61840f5073a2f5 Mon Sep 17 00:00:00 2001 From: skyflow-sapanreddy Date: Thu, 23 Nov 2023 14:41:45 +0530 Subject: [PATCH 03/32] SK-1233 Test fix on reveal-frame --- src/core/internal/frame-elements.ts | 2 +- src/core/internal/reveal/reveal-frame.ts | 2 +- .../core/internal/reveal/reveal-frame.test.js | 59 +++++++++++-------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/core/internal/frame-elements.ts b/src/core/internal/frame-elements.ts index fe22467a..7ccae350 100644 --- a/src/core/internal/frame-elements.ts +++ b/src/core/internal/frame-elements.ts @@ -69,7 +69,7 @@ export default class FrameElements { logLevel); }, ); - const url = window.location.href.split('?')[1]; + const url = window.location?.href.split('?')[1]; const encodedString = decodeURIComponent(url); const parsedRecord = JSON.parse(atob(encodedString)); FrameElements.group = parsedRecord.record; diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 39718a27..366b79dc 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -63,7 +63,7 @@ class RevealFrame { { name: window.name }, () => {}, ); - const url = window.location.href.split('?')[1]; + const url = window.location?.href.split('?')[1]; const encodedString = decodeURIComponent(url); const parsedRecord = JSON.parse(atob(encodedString)); RevealFrame.revealFrame = new RevealFrame(parsedRecord.record, parsedRecord.context); diff --git a/tests/core/internal/reveal/reveal-frame.test.js b/tests/core/internal/reveal/reveal-frame.test.js index 55dcd9b7..74bb1712 100644 --- a/tests/core/internal/reveal/reveal-frame.test.js +++ b/tests/core/internal/reveal/reveal-frame.test.js @@ -42,6 +42,16 @@ const testRecord = { // expect(_on).toHaveBeenCalledTimes(2); // }); // }); +global.window = Object.create(window); +const defineUrl = (url) => { + Object.defineProperty(window, "location", { + value: { + href: url, + }, + writable: true, + }); +}; + const on = jest.fn(); const off = jest.fn(); describe("Reveal Frame Class",()=>{ @@ -58,8 +68,6 @@ describe("Reveal Frame Class",()=>{ }); test("init callback before reveal",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -78,16 +86,13 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; - const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); - emitCb(data); - }); test("init callback after reveal with response value",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -106,10 +111,12 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); - emitCb(data); + // emitCb(data); // reveal response ready const onRevealResponseName = on.mock.calls[0][0]; @@ -121,8 +128,6 @@ describe("Reveal Frame Class",()=>{ }); test("init callback after reveal with response value with mask value",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815", @@ -142,6 +147,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -157,8 +164,6 @@ describe("Reveal Frame Class",()=>{ }); test("init callback after reveal without value",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -182,6 +187,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -197,8 +204,6 @@ describe("Reveal Frame Class",()=>{ }); test("reveal set error",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -225,6 +230,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -243,8 +250,6 @@ describe("Reveal Frame Class",()=>{ }); test("reveal reset error",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -268,6 +273,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -285,8 +292,6 @@ describe("Reveal Frame Class",()=>{ }); test("reveal set token",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -310,6 +315,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -326,8 +333,6 @@ describe("Reveal Frame Class",()=>{ }); test("reveal set altText",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -351,6 +356,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -368,8 +375,6 @@ describe("Reveal Frame Class",()=>{ }); test("reveal clearAltText",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -393,6 +398,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -410,8 +417,6 @@ describe("Reveal Frame Class",()=>{ }); test("copy icon in reveal elements",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -444,6 +449,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); @@ -451,8 +458,6 @@ describe("Reveal Frame Class",()=>{ }) test("global style variant in reveal elements",()=>{ - const testFrame = RevealFrame.init(); - // const onCb = jest.fn(); const data = { record:{ token:"1815-6223-1073-1425", @@ -486,6 +491,8 @@ describe("Reveal Frame Class",()=>{ }, context: { logLevel: LogLevel.ERROR,env:Env.PROD} } + defineUrl('http://localhost/?' + btoa(JSON.stringify(data))); + const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); From c3ca9e4d812e1ddcee06172269bcbc7e9ed4dfdd Mon Sep 17 00:00:00 2001 From: skyflow-sapanreddy Date: Mon, 11 Dec 2023 16:06:17 +0530 Subject: [PATCH 04/32] SK-1233 Test update and safe catching url --- src/core/internal/frame-elements.ts | 9 +++++---- src/core/internal/reveal/reveal-frame.ts | 7 ++++--- tests/core/internal/frame-elements.test.js | 6 ++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/core/internal/frame-elements.ts b/src/core/internal/frame-elements.ts index 7ccae350..748d9813 100644 --- a/src/core/internal/frame-elements.ts +++ b/src/core/internal/frame-elements.ts @@ -69,10 +69,11 @@ export default class FrameElements { logLevel); }, ); - const url = window.location?.href.split('?')[1]; - const encodedString = decodeURIComponent(url); - const parsedRecord = JSON.parse(atob(encodedString)); - FrameElements.group = parsedRecord.record; + const url = window.location?.href; + const configIndex = url.indexOf('?'); + const encodedString = configIndex !== -1 ? decodeURIComponent(url.substring(configIndex + 1)) : ''; + const parsedRecord = encodedString ? JSON.parse(atob(encodedString)) : {}; + FrameElements.group = parsedRecord?.record; if (FrameElements.frameElements) { printLog(parameterizedString(logs.infoLogs.SETUP_IN_START, CLASS_NAME), MessageType.LOG, logLevel); diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 366b79dc..928770d1 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -63,9 +63,10 @@ class RevealFrame { { name: window.name }, () => {}, ); - const url = window.location?.href.split('?')[1]; - const encodedString = decodeURIComponent(url); - const parsedRecord = JSON.parse(atob(encodedString)); + const url = window.location?.href; + const configIndex = url.indexOf('?'); + const encodedString = configIndex !== -1 ? decodeURIComponent(url.substring(configIndex + 1)) : ''; + const parsedRecord = encodedString ? JSON.parse(atob(encodedString)) : {}; RevealFrame.revealFrame = new RevealFrame(parsedRecord.record, parsedRecord.context); } diff --git a/tests/core/internal/frame-elements.test.js b/tests/core/internal/frame-elements.test.js index f5a3da90..d311bb0b 100644 --- a/tests/core/internal/frame-elements.test.js +++ b/tests/core/internal/frame-elements.test.js @@ -78,6 +78,9 @@ describe('test frame elements', () => { windowSpy = jest.spyOn(global, 'window', 'get'); windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:CARD_NUMBER:${btoa('123')}:ERROR`, + location: { + href: `http://localhost/?${btoa(JSON.stringify(element))}`, + } })); emitSpy = jest.spyOn(bus, 'emit'); @@ -129,6 +132,9 @@ describe('test composable frame elements', () => { windowSpy = jest.spyOn(global, 'window', 'get'); windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:group:${btoa('123')}:ERROR`, + location: { + href: `http://localhost/?${btoa(JSON.stringify(element))}`, + } })); emitSpy = jest.spyOn(bus, 'emit'); From c53bbc41d6dbc4b8ecab430cd3b8ec24950ca049 Mon Sep 17 00:00:00 2001 From: skyflow-sapanreddy Date: Wed, 13 Dec 2023 15:44:03 +0530 Subject: [PATCH 05/32] SK-1233 Test update --- tests/core/internal/frame-elements.test.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/core/internal/frame-elements.test.js b/tests/core/internal/frame-elements.test.js index d311bb0b..60e31515 100644 --- a/tests/core/internal/frame-elements.test.js +++ b/tests/core/internal/frame-elements.test.js @@ -115,12 +115,6 @@ describe('test frame elements', () => { } }) const frameElement = new FrameElements(mockCreateElement, {}, 'ERROR') - const heigtEvent = onSpy.mock.calls[0][1]; - const cb = jest.fn(); - heigtEvent({},cb); - cb(); - - }) }) From a419272a5d21b4decb9e76054206c508f9a70b6c Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Fri, 1 Dec 2023 14:15:22 +0530 Subject: [PATCH 06/32] SK-1263 Update README & Samples - Update README for update elements props for skyflow elements - Update Samples for update elements props for skyflow elements --- README.md | 218 +++++++++- .../skyflow-elements-update/.gitignore | 4 + .../skyflow-elements-update/package.json | 15 + .../skyflow-elements-update/src/index.html | 52 +++ .../skyflow-elements-update/src/index.js | 366 ++++++++++++++++ .../skyflow-elements-update.html | 408 ++++++++++++++++++ 6 files changed, 1057 insertions(+), 6 deletions(-) create mode 100644 samples/using-npm/skyflow-elements-update/.gitignore create mode 100644 samples/using-npm/skyflow-elements-update/package.json create mode 100644 samples/using-npm/skyflow-elements-update/src/index.html create mode 100644 samples/using-npm/skyflow-elements-update/src/index.js create mode 100644 samples/using-script-tag/skyflow-elements-update.html diff --git a/README.md b/README.md index c327b962..c9e71cb7 100644 --- a/README.md +++ b/README.md @@ -127,14 +127,16 @@ For `env` parameter, there are 2 accepted values in Skyflow.Env --- # Securely collecting data client-side -- [**Insert data into the vault**](#insert-data-into-the-vault) -- [**Using Skyflow Elements to collect data**](#using-skyflow-elements-to-collect-data) -- [**Using Skyflow Elements to update data**](#using-skyflow-elements-to-update-data) -- [**Using validations on Collect Elements**](#validations) -- [**Event Listener on Collect Elements**](#event-listener-on-collect-elements) -- [**UI Error for Collect Elements**](#ui-error-for-collect-elements) +- [**Insert data into the vault**](#insert-data-into-the-vault) +- [**Using Skyflow Elements to collect data**](#using-skyflow-elements-to-collect-data) +- [**Using Skyflow Elements to update data**](#using-skyflow-elements-to-update-data) +- [**Using validations on Collect Elements**](#validations) +- [**Event Listener on Collect Elements**](#event-listener-on-collect-elements) +- [**UI Error for Collect Elements**](#ui-error-for-collect-elements) - [**Set and Clear value for Collect Elements (DEV ENV ONLY)**](#set-and-clear-value-for-collect-elements-dev-env-only) +- [**Update Collect Elements**](#update-collect-elements) - [**Using Skyflow File Element to upload a file**](#using-skyflow-file-element-to-upload-a-file) + ## Insert data into the vault To insert data into the vault, use the `insert(records, options?)` method of the Skyflow client. The `records` parameter takes a JSON object of the records to insert into the below format. The `options` parameter takes an object of optional parameters for the insertion. The `insert` method also supports upsert operations. @@ -1155,6 +1157,114 @@ cardNumber.clearValue(); ``` +### Update Collect Elements + +You can update collect element properties with the `update` interface. + +The `update` interface takes the below object: + +```javascript +const updateElement = { + table: 'string', // Optional. The table this data belongs to. + column: 'string', // Optional. The column this data belongs to. + inputStyles: {}, // Optional. Styles applied to the form element. + labelStyles: {}, // Optional. Styles for the label of the element. + errorTextStyles: {}, // Optional. Styles for the errorText of element. + label: 'string', // Optional. Label for the form element. + placeholder: 'string', // Optional. Placeholder for the form element. + validations: [], // Optional. Array of validation rules. +}; +``` + +Only include the properties that you want to update for the specified collect element. + +Properties your provided when you created the element remain the same until you explicitly update them. + +`Note`: You can't update the `type` property of an element. + +### End to end example +```javascript +// Create a collect container. +const collectContainer = skyflow.container(Skyflow.ContainerType.COLLECT); + +const stylesOptions = { + inputStyles: { + base: { + fontFamily: 'Inter', + fontStyle: 'normal', + fontWeight: 400, + fontSize: '14px', + lineHeight: '21px', + width: '294px', + }, + }, + labelStyles: {}, + errorTextStyles: { + base: {}, + }, +}; + +// Create collect elements +const cardHolderNameElement = collectContainer.create({ + table: 'pii_fields', + column: 'first_name', + ...stylesOptions, + placeholder: 'Cardholder Name', + type: Skyflow.ElementType.CARDHOLDER_NAME, +}); + +const cardNumberElement = collectContainer.create({ + table: 'pii_fields', + column: 'card_number', + ...stylesOptions, + placeholder: 'Card Number', + type: Skyflow.ElementType.CARD_NUMBER, +}); + +const cvvElement = collectContainer.create({ + table: 'pii_fields', + column: 'cvv', + ...stylesOptions, + placeholder: 'CVV', + type: Skyflow.ElementType.CVV, +}); + +// Mount the collect elements. +collectContainer.mount('#cardHolderNameElement'); // Assumes there is a div with id='#cardHolderNameElement' in the webpage. +collectContainer.mount('#cardNumberElement'); // Assumes there is a div with id='#cardNumberElement' in the webpage. +collectContainer.mount('#cvvElement'); // Assumes there is a div with id='#cvvElement' in the webpage. + +// ... + +// Update validations property on cvvElement. +cvvElement.update({ + validations: [{ + type: Skyflow.ValidationRuleType.LENGTH_MATCH_RULE, + params: { + max: 3, + error: 'cvv must be 3 digits', + }, + }] +}) + +// Update label, placeholder properties on cardHolderNameElement. +cardHolderNameElement.update({ + label: 'CARDHOLDER NAME', + placeholder: 'Eg: John' +}); + +// Update table, column, inputStyles properties on cardNumberElement. +cardNumberElement.update({ + table:'cards', + column:'card_number', + inputStyles:{ + base:{ + color:'blue' + } + } +}); +``` + --- # Securely collecting data client-side using Composable Elements @@ -1752,6 +1862,7 @@ composableContainer.on(Skyflow.EventName.SUBMIT, ()=> { - [**Set token for Reveal Elements**](#set-token-for-reveal-elements) - [**Set and clear altText for Reveal Elements**](#set-and-clear-alttext-for-reveal-elements) - [**Render a file with a File Element**](#render-a-file-with-a-file-element) +- [**Update Reveal Elements**](#update-reveal-elements) ## Retrieving data from the vault @@ -2732,6 +2843,101 @@ fetch("") } ``` +### Update Reveal Elements + +You can update reveal element properties with the `update` interface. + +The `update` interface takes the below object: +```javascript +const updateElement = { + token: 'string', // Optional, token of the data being revealed. + inputStyles: {}, // Optional, styles to be applied to the element. + labelStyles: {}, // Optional, styles to be applied to the label of the reveal element. + errorTextStyles: {}, // Optional, styles that will be applied to the errorText of the reveal element. + label: 'string', // Optional, label for the form element. + altText: 'string', // Optional, string that is shown before reveal, will show token if altText is not provided. + redaction: RedactionType, //Optional, Redaction Type to be applied to data. +}; +``` + +Only include the properties that you want to update for the specified reveal element. + +Properties your provided when you created the element remain the same until you explicitly update them. + +### End to end example +```javascript +// Create a reveal container. +const revealContainer = skyflow.container(Skyflow.ContainerType.REVEAL); + +const stylesOptions = { + inputStyles: { + base: { + fontFamily: 'Inter', + fontStyle: 'normal', + fontWeight: 400, + fontSize: '14px', + lineHeight: '21px', + width: '294px', + }, + }, + labelStyles: {}, + errorTextStyles: { + base: { + color: '#f44336' + }, + }, +}; + +// Create collect elements +const cardHolderNameRevealElement = revealContainer.create({ + token: 'ed5fdd1f-5009-435c-a06b-3417ce76d2c8', + altText: 'first name', + ...stylesOptions, + label: 'Card Holder Name', +}); + +const cardNumberRevealElement = revealContainer.create({ + token: '8ee84061-7107-4faf-bb25-e044f3d191fe', + altText: 'xxxx', + ...stylesOptions, + label: 'Card Number', + redaction: 'RedactionType.CARD_NUMBER' +}); + +// Mount the collect elements. +revealContainer.mount('#cardHolderNameRevealElement'); // Assumes there is a div with id='#cardHolderNameRevealElement' in the webpage. +revealContainer.mount('#cardNumberRevealElement'); // Assumes there is a div with id='#cardNumberRevealElement' in the webpage. + +// ... + +// Update label, labelStyles properties on cardHolderNameRevealElement. +cardHolderNameRevealElement.update({ + label: 'CARDHOLDER NAME', + labelStyles: { + base: { + color: '#aa11aa' + } + } +}); + +// Update inputStyles, errorTextStyles properties on cardNumberRevealElement. +cardNumberRevealElement.update({ + inputStyles: { + base: { + color: '#fff', + backgroundColor: '#000', + borderColor: '#f00', + borderWidth: '5px' + } + }, + errorTextStyles: { + base: { + backgroundColor: '#000', + } + } +}); +``` + --- # Securely deleting data client-side - [**Deleting data from the vault**](#deleting-data-from-the-vault) diff --git a/samples/using-npm/skyflow-elements-update/.gitignore b/samples/using-npm/skyflow-elements-update/.gitignore new file mode 100644 index 00000000..c34cf431 --- /dev/null +++ b/samples/using-npm/skyflow-elements-update/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.parcel-cache/ +dist/ +package-lock.json \ No newline at end of file diff --git a/samples/using-npm/skyflow-elements-update/package.json b/samples/using-npm/skyflow-elements-update/package.json new file mode 100644 index 00000000..db063c2c --- /dev/null +++ b/samples/using-npm/skyflow-elements-update/package.json @@ -0,0 +1,15 @@ +{ + "name": "skyflow-elements-update", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "skyflow-js": "^1.33.0" + } +} diff --git a/samples/using-npm/skyflow-elements-update/src/index.html b/samples/using-npm/skyflow-elements-update/src/index.html new file mode 100644 index 00000000..b7f057ea --- /dev/null +++ b/samples/using-npm/skyflow-elements-update/src/index.html @@ -0,0 +1,52 @@ + + + + + + + Skyflow Elements Update + + + + +
+

Collect Elements

+
+
+
+
+
+ + +
+
+

+      
+
+ +
+

Reveal Elements

+
+
+
+
+
+ + +
+
+ + + diff --git a/samples/using-npm/skyflow-elements-update/src/index.js b/samples/using-npm/skyflow-elements-update/src/index.js new file mode 100644 index 00000000..189026e2 --- /dev/null +++ b/samples/using-npm/skyflow-elements-update/src/index.js @@ -0,0 +1,366 @@ +/* + Copyright (c) 2023 Skyflow, Inc. +*/ +import Skyflow from "skyflow-js"; + +try { + const revealView = document.getElementById("revealView"); + revealView.style.visibility = "hidden"; + const skyflow = Skyflow.init({ + vaultID: "", + vaultURL: "", + getBearerToken: () => { + return new Promise((resolve, reject) => { + const Http = new XMLHttpRequest(); + + Http.onreadystatechange = () => { + if (Http.readyState === 4 && Http.status === 200) { + const response = JSON.parse(Http.responseText); + resolve(response.accessToken); + } + }; + const url = ""; + Http.open("GET", url); + Http.send(); + }); + }, + options: { + logLevel: Skyflow.LogLevel.ERROR, + env: Skyflow.Env.PROD, + }, + }); + + // Create collect Container. + const collectContainer = skyflow.container(Skyflow.ContainerType.COLLECT); + + // Custom styles for collect elements. + const collectStylesOptions = { + inputStyles: { + base: { + border: "1px solid #eae8ee", + padding: "10px 16px", + borderRadius: "4px", + color: "#1d1d1d", + marginTop: "4px", + fontFamily: '"Roboto", sans-serif', + }, + complete: { + color: "#4caf50", + }, + empty: {}, + focus: {}, + invalid: { + color: "#f44336", + }, + global: { + "@import": + 'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + }, + }, + labelStyles: { + base: { + fontSize: "16px", + fontWeight: "bold", + fontFamily: '"Roboto", sans-serif', + }, + global: { + "@import": + 'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + }, + requiredAsterisk: { + color: "red", + }, + }, + errorTextStyles: { + base: { + color: "#f44336", + fontFamily: '"Roboto", sans-serif', + }, + global: { + "@import": + 'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + }, + }, + }; + + // Create collect elements. + const cardNumberElement = collectContainer.create( + { + table: "pii_fields", + column: "card_number", + ...collectStylesOptions, + placeholder: "card number", + label: "Card Number", + type: Skyflow.ElementType.CARD_NUMBER, + }, + { + required: true, + } + ); + + const cvvElement = collectContainer.create({ + table: "pii_fields", + column: "cvv", + ...collectStylesOptions, + label: "Cvv", + placeholder: "cvv", + type: Skyflow.ElementType.CVV, + }); + + const expiryDateElement = collectContainer.create({ + table: "pii_fields", + column: "primary_card.expiry_date", + ...collectStylesOptions, + label: "Expiry Date", + placeholder: "MM/YYYY", + type: Skyflow.ElementType.EXPIRATION_DATE, + }); + + const cardHolderNameElement = collectContainer.create({ + table: "pii_fields", + column: "name", + ...collectStylesOptions, + label: "Card Holder Name", + placeholder: "cardholder name", + type: Skyflow.ElementType.CARDHOLDER_NAME, + }); + + // Mount the elements. + cardNumberElement.mount("#collectCardNumber"); + cvvElement.mount("#collectCvv"); + expiryDateElement.mount("#collectExpiryDate"); + cardHolderNameElement.mount("#collectCardholderName"); + + // Sample helper function to determine cvv length. + const findCvvLength = (cardBinValue) => { + console.log("Came here..!"); + const amexRegex = /^3[78][0-9]{4}$/; + return amexRegex.test(cardBinValue.slice(0, 6)) ? 4 : 3; + }; + + // Validation rules for cvv element. + const length3Rule = { + type: Skyflow.ValidationRuleType.LENGTH_MATCH_RULE, + params: { + max: 3, + error: "cvv must be 3 digits", + }, + }; + + const length4Rule = { + type: Skyflow.ValidationRuleType.LENGTH_MATCH_RULE, + params: { + min: 4, + error: "cvv must be 4 digits", + }, + }; + + // OnChange listener for cardNumber element. + cardNumberElement.on(Skyflow.EventName.CHANGE, (state) => { + console.log("update validation", state); + if (state.isValid) { + // update cvv element validation rule. + if (findCvvLength(state.value) === 3) { + cvvElement.update({ validations: [length3Rule] }); + } else cvvElement.update({ validations: [length4Rule] }); + } + }); + + // update collect elements' properties + const updateCollectElementsButton = document.getElementById( + "updateCollectElements" + ); + if (updateCollectElementsButton) { + updateCollectElementsButton.addEventListener("click", () => { + // update label,placeholder on cardholderName, + cardHolderNameElement.update({ + label: "CARDHOLDER NAME", + placeholder: "Eg: John", + type: Skyflow.ElementType.PIN, + }); + + // update styles on card number + cardNumberElement.update({ + inputStyles: { + base: { + color: "blue", + }, + }, + }); + + // update table,coloumn on expiry date + expiryDateElement.update({ + table: "pii_fields", + column: "expiration_date", + }); + }); + } + + // Collect all elements data. + const collectButton = document.getElementById("collectPCIData"); + if (collectButton) { + collectButton.addEventListener("click", () => { + const collectResponse = collectContainer.collect(); + collectResponse + .then((response) => { + document.getElementById("collectResponse").innerHTML = JSON.stringify( + response, + null, + 2 + ); + }) + .catch((err) => { + console.log(err); + }); + }); + } + + revealView.style.visibility = "visible"; + + const revealStyleOptions = { + inputStyles: { + base: { + border: "1px solid #eae8ee", + padding: "10px 16px", + borderRadius: "4px", + color: "#1d1d1d", + marginTop: "4px", + fontFamily: '"Roboto", sans-serif', + }, + global: { + "@import": + 'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + }, + }, + labelStyles: { + base: { + fontSize: "16px", + fontWeight: "bold", + fontFamily: '"Roboto", sans-serif', + }, + global: { + "@import": + 'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + }, + }, + errorTextStyles: { + base: { + color: "#f44336", + paddingLeft: "20px", + fontFamily: '"Roboto", sans-serif', + }, + global: { + "@import": + 'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + }, + }, + }; + + // Create Reveal Elements With Tokens. + const fieldsTokenData = response.records[0].fields; + const revealContainer = skyflow.container(Skyflow.ContainerType.REVEAL); + const revealCardNumberElement = revealContainer.create({ + token: fieldsTokenData.card_number, + label: "Card Number", + ...revealStyleOptions, + }); + revealCardNumberElement.mount("#revealCardNumber"); + + const revealCardCvvElement = revealContainer.create({ + token: fieldsTokenData.cvv, + label: "CVV", + ...revealStyleOptions, + altText: "###", + }); + revealCardCvvElement.mount("#revealCvv"); + + const revealCardExpiryElement = revealContainer.create({ + token: fieldsTokenData.expiration_date, + label: "Card Expiry Date", + ...revealStyleOptions, + }); + revealCardExpiryElement.mount("#revealExpiryDate"); + + const revealCardholderNameElement = revealContainer.create({ + token: fieldsTokenData.name, + label: "Card Holder Name", + ...revealStyleOptions, + }); + revealCardholderNameElement.mount("#revealCardholderName"); + + const revealButton = document.getElementById("revealPCIData"); + + // update Reveal elements' properties + const updateRevealElementsButton = document.getElementById( + "updateRevealElements" + ); + if (updateRevealElementsButton) { + updateRevealElementsButton.addEventListener("click", () => { + // update label,inputStyles on cardholderName, + revealCardholderNameElement.update({ + label: "CARDHOLDER NAME", + inputStyles: { + base: { + color: "#aa11aa", + }, + }, + }); + + // update label,labelSyles on card number + revealCardNumberElement.update({ + label: "CARD NUMBER", + labelStyles: { + base: { + borderWidth: "5px", + }, + }, + }); + + // update redaction,inputStyles on expiry date + revealCardExpiryElement.update({ + redaction: Skyflow.RedactionType.REDACTED, + inputStyles: { + base: { + backgroundColor: "#000", + color: "#fff", + }, + }, + }); + + // update altText,token,inputStyles,errorTextStyles on cvv + revealCardCvvElement.update({ + altText: "XXXX-XX", + token: "new-random-roken", + inputStyles: { + base: { + color: "#fff", + backgroundColor: "#000", + borderColor: "#f00", + borderWidth: "5px", + }, + }, + errorTextStyles: { + base: { + backgroundColor: "#000", + border: "1px #f00 solid", + }, + }, + }); + }); + } + + if (revealButton) { + revealButton.addEventListener("click", () => { + revealContainer + .reveal() + .then((res) => { + console.log(res); + }) + .catch((err) => { + console.log(err); + }); + }); + } +} catch (err) { + console.log(err); +} diff --git a/samples/using-script-tag/skyflow-elements-update.html b/samples/using-script-tag/skyflow-elements-update.html new file mode 100644 index 00000000..eb2a372b --- /dev/null +++ b/samples/using-script-tag/skyflow-elements-update.html @@ -0,0 +1,408 @@ + + + + + + + Skyflow Elements Update + + + + + +
+

Collect Elements

+
+
+
+
+
+ + +
+
+

+      
+
+ +
+

Reveal Elements

+
+
+
+
+
+ + +
+
+ + + + + From c0261139d1afa52df74b863f0f6da5bdfc1e8524 Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Thu, 30 Nov 2023 19:05:09 +0530 Subject: [PATCH 07/32] SK-1238 Update element props for Collect and Reveal Elements - SK-1260 TDD Update element props for Collect Elements - SK-1261 TDD Update elements props for Reveal Elements --- src/core/constants.ts | 1 + .../external/collect/collect-container.ts | 8 +- src/core/external/collect/collect-element.ts | 26 ++++- src/core/external/reveal/reveal-element.ts | 16 +++ src/core/internal/reveal/reveal-frame.ts | 98 +++++++++++++++---- src/utils/logs.ts | 2 +- .../external/collect/collect-element.test.js | 83 +++++++++++++++- .../external/reveal/reveal-element.test.js | 22 ++++- .../core/internal/reveal/reveal-frame.test.js | 72 ++++++++++++++ 9 files changed, 298 insertions(+), 30 deletions(-) diff --git a/src/core/constants.ts b/src/core/constants.ts index 0aed605e..be4c3ef8 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -79,6 +79,7 @@ export const ELEMENT_EVENTS_TO_IFRAME = { export const REVEAL_ELEMENT_OPTIONS_TYPES = { TOKEN: 'TOKEN', ALT_TEXT: 'ALT_TEXT', + ELEMENT_PROPS: 'ELEMENT_PROPS', }; export const ELEMENT_EVENTS_TO_CONTAINER = { diff --git a/src/core/external/collect/collect-container.ts b/src/core/external/collect/collect-container.ts index b14c2dab..ea9d0c56 100644 --- a/src/core/external/collect/collect-container.ts +++ b/src/core/external/collect/collect-container.ts @@ -198,9 +198,9 @@ class CollectContainer extends Container { let element = this.#elements[tempElements.elementName]; if (element) { if (isSingleElementAPI) { - element.update(elements[0]); + element.updateElementGroup(elements[0]); } else { - element.update(tempElements); + element.updateElementGroup(tempElements); } } else { const elementId = uuid(); @@ -229,7 +229,7 @@ class CollectContainer extends Container { if (!this.#elements[name]) { this.#elements[name] = this.create(iElement.elementType, element); } else { - this.#elements[name].update(iElement); + this.#elements[name].updateElementGroup(iElement); } }); } @@ -251,7 +251,7 @@ class CollectContainer extends Container { #updateCallback = (elements: any[]) => { elements.forEach((element) => { if (this.#elements[element.elementName]) { - this.#elements[element.elementName].update(element); + this.#elements[element.elementName].updateElementGroup(element); } }); }; diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 46a346dd..6bcacbf6 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -22,7 +22,9 @@ import { import SkyflowError from '../../../libs/skyflow-error'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import logs from '../../../utils/logs'; -import { Context, Env, MessageType } from '../../../utils/common'; +import { + Context, Env, EventName, MessageType, +} from '../../../utils/common'; import { formatFrameNameToId, getReturnValue } from '../../../utils/helpers'; import SkyflowElement from '../common/skyflow-element'; import { ContainerType } from '../../../skyflow'; @@ -72,6 +74,8 @@ class CollectElement extends SkyflowElement { #readyToMount: boolean = false; + #isUpdateCalled = false; + constructor( elementId: string, elementGroup: any, @@ -223,7 +227,7 @@ class CollectElement extends SkyflowElement { this.#iframe.unmount(); }; - update = (group) => { + updateElementGroup = (group) => { let tempGroup = deepClone(group); const callback = () => { @@ -297,6 +301,20 @@ class CollectElement extends SkyflowElement { }); }; + update = (options) => { + this.#isUpdateCalled = true; + if (this.#mounted) { + this.updateElement({ elementName: this.#group.elementName, ...options }); + this.#isUpdateCalled = false; + } else if (this.#isUpdateCalled) { + this.#eventEmitter.on(`${EventName.READY}:${this.#elementId}`, () => { + this.updateElement({ elementName: this.#group.elementName, ...options }); + this.#mounted = true; + this.#isUpdateCalled = false; + }); + } + }; + #updateState = () => { this.#states.forEach((elementState, index) => { if (index === 0) { @@ -489,6 +507,10 @@ class CollectElement extends SkyflowElement { return this.#mounted; } + isUpdateCalled():boolean { + return this.#isUpdateCalled; + } + isValidElement():boolean { for (let i = 0; i < this.#elements.length; i += 1) { if (!Object.prototype.hasOwnProperty.call(this.#elements[i], 'table')) { diff --git a/src/core/external/reveal/reveal-element.ts b/src/core/external/reveal/reveal-element.ts index 9821f50d..a9923c93 100644 --- a/src/core/external/reveal/reveal-element.ts +++ b/src/core/external/reveal/reveal-element.ts @@ -60,6 +60,8 @@ class RevealElement extends SkyflowElement { #clientId: string; + #isUpdateCalled = false; + constructor(record: IRevealElementInput, options: IRevealElementOptions = {}, metaData: any, container: any, elementId: string, context: Context) { @@ -347,6 +349,20 @@ class RevealElement extends SkyflowElement { } this.#iframe.unmount(); } + + update(options) { + this.#isUpdateCalled = true; + this.#recordData = { + ...this.#recordData, + ...options, + }; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + updatedValue: options, + }); + this.#isUpdateCalled = false; + } } export default RevealElement; diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 928770d1..77571ced 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -138,6 +138,26 @@ class RevealFrame { } } + if ( + Object.prototype.hasOwnProperty.call(this.#record, 'errorTextStyles') + && Object.prototype.hasOwnProperty.call(this.#record.errorTextStyles, STYLE_TYPE.BASE) + ) { + this.#errorTextStyles = {}; + this.#errorTextStyles[STYLE_TYPE.BASE] = { + ...REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES[STYLE_TYPE.BASE], + ...this.#record.errorTextStyles[STYLE_TYPE.BASE], + }; + getCssClassesFromJss(this.#errorTextStyles, 'error'); + if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { + generateCssWithoutClass(this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]); + } + } else { + getCssClassesFromJss( + REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES, + 'error', + ); + } + this.#elementContainer.appendChild(this.#dataElememt); document.body.append(this.#elementContainer); @@ -185,25 +205,6 @@ class RevealFrame { private setRevealError(errorText: string) { this.#errorElement.innerText = errorText; - if ( - Object.prototype.hasOwnProperty.call(this.#record, 'errorTextStyles') - && Object.prototype.hasOwnProperty.call(this.#record.errorTextStyles, STYLE_TYPE.BASE) - ) { - this.#errorTextStyles = {}; - this.#errorTextStyles[STYLE_TYPE.BASE] = { - ...REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES[STYLE_TYPE.BASE], - ...this.#record.errorTextStyles[STYLE_TYPE.BASE], - }; - getCssClassesFromJss(this.#errorTextStyles, 'error'); - if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { - generateCssWithoutClass(this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]); - } - } else { - getCssClassesFromJss( - REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES, - 'error', - ); - } this.#elementContainer.appendChild(this.#errorElement); } @@ -227,11 +228,70 @@ class RevealFrame { token: data.updatedValue, }; this.updateDataView(); + } else if (data.updateType === REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS) { + const updatedValue = data.updatedValue as object; + this.#record = { + ...this.#record, + ...updatedValue, + }; + this.updateElementProps(); } } }); } + private updateElementProps() { + this.updateDataView(); + if (Object.prototype.hasOwnProperty.call(this.#record, 'label')) { + this.#labelElement.innerText = this.#record.label; + } + if (Object.prototype.hasOwnProperty.call(this.#record, 'inputStyles')) { + this.#inputStyles[STYLE_TYPE.BASE] = { + ...this.#inputStyles, + ...this.#record.inputStyles[STYLE_TYPE.BASE], + }; + getCssClassesFromJss(this.#inputStyles, 'content'); + if (this.#record.inputStyles[STYLE_TYPE.GLOBAL]) { + const newInputGlobalStyles = { + ...this.#inputStyles[STYLE_TYPE.GLOBAL], + ...this.#record.inputStyles[STYLE_TYPE.GLOBAL], + }; + generateCssWithoutClass(newInputGlobalStyles); + } + } + if (Object.prototype.hasOwnProperty.call(this.#record, 'labelStyles')) { + this.#labelStyles[STYLE_TYPE.BASE] = { + ...this.#labelStyles, + ...REVEAL_ELEMENT_LABEL_DEFAULT_STYLES[STYLE_TYPE.BASE], + ...this.#record.labelStyles[STYLE_TYPE.BASE], + }; + getCssClassesFromJss(this.#labelStyles, 'label'); + + if (this.#record.labelStyles[STYLE_TYPE.GLOBAL]) { + const newLabelGlobalStyles = { + ...this.#labelStyles[STYLE_TYPE.GLOBAL], + ...this.#record.labelStyles[STYLE_TYPE.GLOBAL], + }; + generateCssWithoutClass(newLabelGlobalStyles); + } + } + if (Object.prototype.hasOwnProperty.call(this.#record, 'errorTextStyles')) { + this.#errorTextStyles[STYLE_TYPE.BASE] = { + ...REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES[STYLE_TYPE.BASE], + ...this.#errorTextStyles[STYLE_TYPE.BASE], + ...this.#record.errorTextStyles[STYLE_TYPE.BASE], + }; + getCssClassesFromJss(this.#errorTextStyles, 'error'); + if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { + const newErrorTextGlobalStyles = { + ...this.#errorTextStyles[STYLE_TYPE.GLOBAL], + ...this.#record.errorTextStyles[STYLE_TYPE.GLOBAL], + }; + generateCssWithoutClass(newErrorTextGlobalStyles); + } + } + } + private updateDataView() { if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { this.#dataElememt.innerText = this.#record.altText; diff --git a/src/utils/logs.ts b/src/utils/logs.ts index a24ad3ed..13c245ce 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -197,7 +197,7 @@ const logs = { MISSING_SKYFLOWID_IN_COLLECT: 'Interface: collect element - "skyflowID" key is required in file type element', EMPTY_COLUMN_IN_COLLECT: 'Interface: collect element - column cannot be empty.', INVALID_COLUMN_IN_COLLECT: 'Interface: collect element - Invalid column. column of type string is required', - UNIQUE_ELEMENT_NAME: 'The element name has to unique: %s1', + UNIQUE_ELEMENT_NAME: 'The element name has to be unique: %s1', ELEMENTS_NOT_MOUNTED: 'Interface: collect container - Elements should be mounted before invoking collect', DUPLICATE_ELEMENT: 'Interface: collect container - Duplicate column %s1 found in %s2.', DUPLICATE_ELEMENT_ADDITIONAL_FIELDS: 'Interface: collect container - Duplicate column %s1 found in %s2 in additional fields', diff --git a/tests/core/external/collect/collect-element.test.js b/tests/core/external/collect/collect-element.test.js index efa15944..ab68c068 100644 --- a/tests/core/external/collect/collect-element.test.js +++ b/tests/core/external/collect/collect-element.test.js @@ -403,7 +403,7 @@ describe('collect element', () => { setTimeout(()=>{ expect(element.isMounted()).toBe(true); },0); - element.update(updateElementInput); + element.updateElementGroup(updateElementInput); element.unmount(); }); @@ -437,7 +437,7 @@ describe('collect element', () => { setTimeout(()=>{ expect(element.isMounted()).toBe(true); },0); - element.update(updateElementInput); + element.updateElementGroup(updateElementInput); element.unmount(); }); @@ -473,7 +473,7 @@ describe('collect element', () => { setTimeout(()=>{ expect(element.isMounted()).toBe(true); },0); - element.update(updateElementInput); + element.updateElementGroup(updateElementInput); element.unmount(); }); @@ -879,4 +879,81 @@ describe('collect element methods', () => { console.log(err); } }); + + it('update element in DEV environment', () => { + const collectElement = new CollectElement(id, + { elementName,rows }, + {}, + 'containerId', + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD }, + ); + + const emitter = jest.spyOn(bus, 'emit'); + const testUpdateOptions = { + table: 'table', + inputStyles: { + base: { + color: 'blue' + } + } + }; + expect(collectElement.isMounted()).toBe(false); + expect(collectElement.isUpdateCalled()).toBe(false); + collectElement.update(testUpdateOptions); + + console.log(emitter.mock.calls); + console.log(onSpy.mock.calls); + }); + + it('update element in DEV environment when element is mounted', () => { + const collectElement = new CollectElement(id, + { elementName,rows }, + {}, + 'containerId', + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD } + ); + + const testUpdateOptions = { + table: 'table', + inputStyles: { + base: { + color: 'blue' + } + } + }; + + expect(collectElement.isMounted()).toBe(false); + expect(collectElement.isUpdateCalled()).toBe(false); + + const div = document.createElement('div'); + collectElement.mount(div) + collectElement.update(testUpdateOptions); + + setTimeout(() => { + expect(collectElement.isMounted()).toBe(true); + expect(collectElement.updateElement).toBeCalledTimes(1); + expect(collectElement.isUpdateCalled()).toBe(false); + }, 0); + collectElement.unmount() + }); + + it('update element in PROD environment', () => { + const testUpdateOptions = { + table: 'table', + inputStyles: { + base: { + color: 'blue' + } + } + }; + expect(testCollectElementProd.isMounted()).toBe(false); + expect(testCollectElementProd.isUpdateCalled()).toBe(false); + testCollectElementProd.update(testUpdateOptions); + }); }); \ No newline at end of file diff --git a/tests/core/external/reveal/reveal-element.test.js b/tests/core/external/reveal/reveal-element.test.js index e56526df..057db99e 100644 --- a/tests/core/external/reveal/reveal-element.test.js +++ b/tests/core/external/reveal/reveal-element.test.js @@ -407,5 +407,25 @@ describe("Reveal Element Methods",()=>{ const documentElements = document.querySelectorAll('span'); testRevealElement2.mount('#mockElement'); testRevealElement2.unmount(); -}); + }); + + it('should update the properties of elements', () => { + const { window } = new JSDOM('
'); + document = window.document; + const element = document.createElement('div'); + element.setAttribute('id', '#mockElement'); + const documentElements = document.querySelectorAll('span'); + testRevealElement2.mount('#mockElement'); + + const testUpdateOptions = { + label: 'Updated Label', + inputStyles: { + base: { + borderWitdth: '5px', + } + } + } + + testRevealElement2.update(testUpdateOptions); + }) }); diff --git a/tests/core/internal/reveal/reveal-frame.test.js b/tests/core/internal/reveal/reveal-frame.test.js index 74bb1712..203c1527 100644 --- a/tests/core/internal/reveal/reveal-frame.test.js +++ b/tests/core/internal/reveal/reveal-frame.test.js @@ -499,4 +499,76 @@ describe("Reveal Frame Class",()=>{ emitCb(data); }) + test('update reveal element props', () => { + const testFrame = RevealFrame.init(); + // const onCb = jest.fn(); + const data = { + record:{ + token:"1815-6223-1073-1425", + label:"Card Number", + altText:"xxxx-xxxx-xxxx-xxxx", + inputStyles:{ + base:{ + color:"red" + } + }, + labelStyles:{ + base:{ + color:"black" + } + }, + errorTextStyles:{ + base:{ + color:"red" + } + } + }, + context: { logLevel: LogLevel.ERROR,env:Env.PROD} + } + + const testUpdateOptions = { + label: 'Updated Label', + altText: 'updated alt', + token: 'test-token-re-1', + inputStyles: { + base: { + borderWitdth: '5px', + }, + global: { + '@import' :'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + } + }, + labelStyles: { + base: { + color: 'red' + }, + global: { + '@import' :'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + } + }, + errorTextStyles: { + base: { + backgroundColor: 'black' + }, + global: { + '@import' :'url("https://fonts.googleapis.com/css2?family=Roboto&display=swap")', + } + } + } + + const emittedEventName = emitSpy.mock.calls[0][0]; + const emitCb = emitSpy.mock.calls[0][2]; + expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_FRAME_READY); + emitCb(data); + + const onRevealResponseName = on.mock.calls[2][0]; + expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS); + const onRevealResponseCb = on.mock.calls[2][1]; + onRevealResponseCb({ + name: "", + updateType:REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + updatedValue:testUpdateOptions, + }); + }); + }); From 7b12eee2ad0844b27e71fe4510646cf05809ec15 Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Mon, 11 Dec 2023 19:38:33 +0530 Subject: [PATCH 08/32] SK-1238 Ability to update skyflow elements' properties dynamically - Increased coveragey --- .../external/collect/collect-element.test.js | 241 ++++++++++++------ 1 file changed, 163 insertions(+), 78 deletions(-) diff --git a/tests/core/external/collect/collect-element.test.js b/tests/core/external/collect/collect-element.test.js index ab68c068..5aacb3d8 100644 --- a/tests/core/external/collect/collect-element.test.js +++ b/tests/core/external/collect/collect-element.test.js @@ -9,6 +9,7 @@ import { ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME } from '../../../../ import SKYFLOW_ERROR_CODE from '../../../../src/utils/constants'; import { checkForElementMatchRule } from '../../../../src/core-utils/collect'; import { ContainerType } from '../../../../src/skyflow'; +import EventEmitter from '../../../../src/event-emitter'; const elementName = 'element:CVV:cGlpX2ZpZWxkcy5wcmltYXJ5X2NhcmQuY3Z2'; const id = 'id'; @@ -112,11 +113,15 @@ const groupEmiitter = { }) } +jest.mock('../../../../src/event-emitter'); +let emitterSpy; +EventEmitter.mockImplementation(() => ({ + on: jest.fn().mockImplementation((name, cb) => {emitterSpy = cb}), + _emit: jest.fn() +})); + const on = jest.fn(); describe('collect element', () => { - - - let emitSpy; let targetSpy; beforeEach(() => { @@ -493,6 +498,90 @@ describe('collect element', () => { const options = element.getOptions(); expect(options.name).toBe(input.column); }); + + it('updates element properties when element is mounted', () => { + const onSpy = jest.spyOn(bus, 'on'); + const element = new CollectElement(id, + { elementName, rows }, + {}, + { type: ContainerType.COLLECT, containerId: 'containerId' }, + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD } + ); + + const inputEvent = onSpy.mock.calls + .filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT); + const inputCb = inputEvent[0][1]; + const cb2 = jest.fn(); + + const mountedEvent = onSpy.mock.calls + .filter((data)=> data[0] === ELEMENT_EVENTS_TO_CLIENT.MOUNTED); + const mountCb = mountedEvent[0][1]; + const cb3 = jest.fn(); + + inputCb({ + name: elementName, + event: ELEMENT_EVENTS_TO_CLIENT.READY, + value: {}, + }, cb2); + + expect(element.isMounted()).toBe(false); + mountCb({name: elementName}, cb3); + expect(element.isMounted()).toBe(true); + element.update({ label :'Henry' }); + }); + + it('updates element properties when element is not mounted', () => { + const element = new CollectElement(id, + { elementName, rows }, + {}, + { type: ContainerType.COLLECT, containerId: 'containerId' }, + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD } + ); + expect(element.isMounted()).toBe(false); + element.update({ label :'Henry' }); + emitterSpy(); + expect(element.isMounted()).toBe(true); + }); + + it('update element group', () => { + const onSpy = jest.spyOn(bus, 'on'); + const element = new CollectElement(id, + { elementName, rows }, + {}, + { type: ContainerType.COLLECT, containerId: 'containerId' }, + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD } + ); + + const inputEvent = onSpy.mock.calls + .filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT); + const inputCb = inputEvent[0][1]; + const cb2 = jest.fn(); + + const mountedEvent = onSpy.mock.calls + .filter((data)=> data[0] === ELEMENT_EVENTS_TO_CLIENT.MOUNTED); + const mountCb = mountedEvent[0][1]; + const cb3 = jest.fn(); + + inputCb({ + name: elementName, + event: ELEMENT_EVENTS_TO_CLIENT.READY, + value: {}, + }, cb2); + + expect(element.isMounted()).toBe(false); + mountCb({name: elementName}, cb3); + expect(element.isMounted()).toBe(true); + element.updateElementGroup({ elementName, rows }); + }); }); const row = { @@ -880,80 +969,76 @@ describe('collect element methods', () => { } }); - it('update element in DEV environment', () => { - const collectElement = new CollectElement(id, - { elementName,rows }, - {}, - 'containerId', - true, - destroyCallback, - updateCallback, - { logLevel: LogLevel.ERROR, env: Env.PROD }, - ); - - const emitter = jest.spyOn(bus, 'emit'); - const testUpdateOptions = { - table: 'table', - inputStyles: { - base: { - color: 'blue' - } - } - }; - expect(collectElement.isMounted()).toBe(false); - expect(collectElement.isUpdateCalled()).toBe(false); - collectElement.update(testUpdateOptions); - - console.log(emitter.mock.calls); - console.log(onSpy.mock.calls); - }); - - it('update element in DEV environment when element is mounted', () => { - const collectElement = new CollectElement(id, - { elementName,rows }, - {}, - 'containerId', - true, - destroyCallback, - updateCallback, - { logLevel: LogLevel.ERROR, env: Env.PROD } - ); - - const testUpdateOptions = { - table: 'table', - inputStyles: { - base: { - color: 'blue' - } - } - }; - - expect(collectElement.isMounted()).toBe(false); - expect(collectElement.isUpdateCalled()).toBe(false); - - const div = document.createElement('div'); - collectElement.mount(div) - collectElement.update(testUpdateOptions); - - setTimeout(() => { - expect(collectElement.isMounted()).toBe(true); - expect(collectElement.updateElement).toBeCalledTimes(1); - expect(collectElement.isUpdateCalled()).toBe(false); - }, 0); - collectElement.unmount() - }); + // it('update element in DEV environment', () => { + // const collectElement = new CollectElement(id, + // { elementName,rows }, + // {}, + // 'containerId', + // true, + // destroyCallback, + // updateCallback, + // { logLevel: LogLevel.ERROR, env: Env.DEV }, + // ); + + // const testUpdateOptions = { + // table: 'table', + // inputStyles: { + // base: { + // color: 'blue' + // } + // } + // }; + // expect(collectElement.isMounted()).toBe(false); + // expect(collectElement.isUpdateCalled()).toBe(false); + // collectElement.update(testUpdateOptions); + // }); + + // it('update element in DEV environment when element is mounted', () => { + // const collectElement = new CollectElement(id, + // { elementName,rows }, + // {}, + // 'containerId', + // true, + // destroyCallback, + // updateCallback, + // { logLevel: LogLevel.ERROR, env: Env.PROD } + // ); + + // const testUpdateOptions = { + // table: 'table', + // inputStyles: { + // base: { + // color: 'blue' + // } + // } + // }; + + // expect(collectElement.isMounted()).toBe(false); + // expect(collectElement.isUpdateCalled()).toBe(false); + + // const div = document.createElement('div'); + // collectElement.mount(div) + // collectElement.update(testUpdateOptions); + + // setTimeout(() => { + // expect(collectElement.isMounted()).toBe(true); + // expect(collectElement.updateElement).toBeCalledTimes(1); + // expect(collectElement.isUpdateCalled()).toBe(false); + // }, 0); + // collectElement.unmount(); + // }); - it('update element in PROD environment', () => { - const testUpdateOptions = { - table: 'table', - inputStyles: { - base: { - color: 'blue' - } - } - }; - expect(testCollectElementProd.isMounted()).toBe(false); - expect(testCollectElementProd.isUpdateCalled()).toBe(false); - testCollectElementProd.update(testUpdateOptions); - }); + // it('update element in PROD environment', () => { + // const testUpdateOptions = { + // table: 'table', + // inputStyles: { + // base: { + // color: 'blue' + // } + // } + // }; + // expect(testCollectElementProd.isMounted()).toBe(false); + // expect(testCollectElementProd.isUpdateCalled()).toBe(false); + // testCollectElementProd.update(testUpdateOptions); + // }); }); \ No newline at end of file From dc0ae0b0057b1547eda01f6fb09b2cfc08e678eb Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Tue, 12 Dec 2023 17:00:42 +0530 Subject: [PATCH 09/32] SK-1238 Update element props dynamically --- src/core/external/reveal/reveal-element.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/external/reveal/reveal-element.ts b/src/core/external/reveal/reveal-element.ts index a9923c93..60702608 100644 --- a/src/core/external/reveal/reveal-element.ts +++ b/src/core/external/reveal/reveal-element.ts @@ -60,8 +60,6 @@ class RevealElement extends SkyflowElement { #clientId: string; - #isUpdateCalled = false; - constructor(record: IRevealElementInput, options: IRevealElementOptions = {}, metaData: any, container: any, elementId: string, context: Context) { @@ -351,7 +349,6 @@ class RevealElement extends SkyflowElement { } update(options) { - this.#isUpdateCalled = true; this.#recordData = { ...this.#recordData, ...options, @@ -361,7 +358,6 @@ class RevealElement extends SkyflowElement { updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, updatedValue: options, }); - this.#isUpdateCalled = false; } } From 103ed28e20b59d37ec2a87632ec7e0ff701a7a21 Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Tue, 12 Dec 2023 11:48:04 +0000 Subject: [PATCH 10/32] [AUTOMATED] Private Release 1.33.4-dev.7ab9da9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45692669..2ad36500 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.33.4", + "version": "1.33.4-dev.7ab9da9", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From cfde97109804dc76035e0afb86c71e9c5d0e6bbf Mon Sep 17 00:00:00 2001 From: appasuamithsai Date: Tue, 5 Dec 2023 16:08:00 +0530 Subject: [PATCH 11/32] SK-1258 fix invalid input styles when custom validations are applied on expiration month expiration date collect elements js sdk --- src/core/internal/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 13fb15b0..f7ef9370 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -260,7 +260,7 @@ export class FrameElement { this.iFrameFormElement.on(ELEMENT_EVENTS_TO_CLIENT.CHANGE, (state) => { // On CHANGE set isEmpty to false - state.isEmpty = false; + state.isEmpty = !state.value; if ( state.value From 5609e2e6b548b58bc9ccbdc9f5585401439da27b Mon Sep 17 00:00:00 2001 From: yaswanth-pula-skyflow Date: Tue, 12 Dec 2023 11:52:23 +0000 Subject: [PATCH 12/32] [AUTOMATED] Private Release 1.33.4-dev.71819a5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ad36500..b7acad72 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.33.4-dev.7ab9da9", + "version": "1.33.4-dev.71819a5", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 30ece227b1aed476454272e80df9a82b0d573f4e Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Wed, 13 Dec 2023 14:37:50 +0530 Subject: [PATCH 13/32] SK-1238 Ability to update element props dynamically - Added support for updating skyflowID dynamically --- src/core/internal/index.ts | 4 ++++ tests/core/internal/frame-controller.test.js | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index f7ef9370..2f06966d 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -339,6 +339,7 @@ export class FrameElement { inputStyles, labelStyles, errorTextStyles, + skyflowID, } = data.options; if (validations) { this.iFrameFormElement.validations = validations; @@ -369,6 +370,9 @@ export class FrameElement { if (labelStyles) { this.injectInputStyles(labelStyles, 'label'); } + if (skyflowID) { + this.iFrameFormElement.skyflowID = skyflowID; + } } }); diff --git a/tests/core/internal/frame-controller.test.js b/tests/core/internal/frame-controller.test.js index a35a29f9..f0eb1f60 100644 --- a/tests/core/internal/frame-controller.test.js +++ b/tests/core/internal/frame-controller.test.js @@ -212,7 +212,8 @@ describe('test frame controller', () => { min:2, } } - ] + ], + skyflowID: 'updated-skyflow-id', }, }); From e778c853e2d0e84672841426956d4c4a7bd424a9 Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Wed, 13 Dec 2023 11:12:40 +0000 Subject: [PATCH 14/32] [AUTOMATED] Private Release 1.33.4-dev.04bbcfc --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b7acad72..d6d7bc43 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.33.4-dev.71819a5", + "version": "1.33.4-dev.04bbcfc", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 2bf321cc582585463f1b75b6bf0ca367819e2bf1 Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:44:50 +0530 Subject: [PATCH 15/32] SK-1138 iframe height resize (#357) * SK-1138 iframe height resize * SK-1138 fix test cases * SK-1138 updated test cases --- src/core/external/collect/collect-element.ts | 21 ++++++- .../collect/collect-container.test.js | 5 ++ .../external/collect/collect-element.test.js | 60 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 6bcacbf6..4bcc3cd1 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -45,6 +45,8 @@ class CollectElement extends SkyflowElement { #elements: any[]; + resizeObserver: ResizeObserver | null; + #state = { isEmpty: true, isComplete: false, @@ -95,7 +97,7 @@ class CollectElement extends SkyflowElement { this.#group = validateAndSetupGroupOptions(elementGroup); this.#elements = getElements(elementGroup); this.#isSingleElementAPI = isSingleElementAPI; - + this.resizeObserver = null; if (groupEventEmitter) this.#groupEmitter = groupEventEmitter; // if (this.#isSingleElementAPI && this.#elements.length > 1) { // throw new SkyflowError(SKYFLOW_ERROR_CODE.UNKNOWN_ERROR, [], true); @@ -165,6 +167,12 @@ class CollectElement extends SkyflowElement { if (!domElement) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['CollectElement'], true); } + this.resizeObserver = new ResizeObserver(() => { + this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + }); const sub = (data, callback) => { if (data.name === this.#iframe.name) { callback(this.#group); @@ -221,10 +229,21 @@ class CollectElement extends SkyflowElement { } }); } + if (typeof domElement === 'string') { + const targetElement = document.querySelector(domElement); + if (targetElement) { + this.resizeObserver?.observe(targetElement); + } + } else if (domElement instanceof HTMLElement) { + this.resizeObserver?.observe(domElement); + } }; unmount = () => { this.#iframe.unmount(); + if (this.resizeObserver) { + this.resizeObserver?.disconnect(); + } }; updateElementGroup = (group) => { diff --git a/tests/core/external/collect/collect-container.test.js b/tests/core/external/collect/collect-container.test.js index 3e5cc72f..3cbecc1e 100644 --- a/tests/core/external/collect/collect-container.test.js +++ b/tests/core/external/collect/collect-container.test.js @@ -15,6 +15,11 @@ import { LogLevel, Env, ValidationRuleType } from '../../../../src/utils/common' import SKYFLOW_ERROR_CODE from '../../../../src/utils/constants'; import logs from '../../../../src/utils/logs'; +global.ResizeObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn(), +})); + const bus = require('framebus'); iframerUtils.getIframeSrc = jest.fn(() => ('https://google.com')); diff --git a/tests/core/external/collect/collect-element.test.js b/tests/core/external/collect/collect-element.test.js index 5aacb3d8..be1e4354 100644 --- a/tests/core/external/collect/collect-element.test.js +++ b/tests/core/external/collect/collect-element.test.js @@ -11,6 +11,11 @@ import { checkForElementMatchRule } from '../../../../src/core-utils/collect'; import { ContainerType } from '../../../../src/skyflow'; import EventEmitter from '../../../../src/event-emitter'; +global.ResizeObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn(), +})); + const elementName = 'element:CVV:cGlpX2ZpZWxkcy5wcmltYXJ5X2NhcmQuY3Z2'; const id = 'id'; const input = { @@ -886,6 +891,7 @@ describe('collect element validations', () => { }); describe('collect element methods', () => { + const emitSpy = jest.spyOn(bus, 'emit'); const onSpy = jest.spyOn(bus, 'on'); const testCollectElementProd = new CollectElement(id, { @@ -968,6 +974,60 @@ describe('collect element methods', () => { console.log(err); } }); + it('should create a ResizeObserver when mounted', () => { + const testCollectElementProd = new CollectElement(id, + { + elementName, + rows, + }, + {}, + 'containerId', + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD }); + let div = document.createElement('div') + div.setAttribute('id', 'id1') + testCollectElementProd.mount(div); + + expect(ResizeObserver).toHaveBeenCalled(); + expect(testCollectElementProd.resizeObserver.observe).toHaveBeenCalledWith( + div + ); + div.style.display = 'none' + expect(ResizeObserver).toHaveBeenCalled(); + expect(testCollectElementProd.resizeObserver.observe).toHaveBeenCalledWith( + div + ); + testCollectElementProd.unmount(); + expect(ResizeObserver).toHaveBeenCalled(); + expect(testCollectElementProd.resizeObserver.disconnect).toHaveBeenCalled(); + + }); + it('ResizeObserver should get disconnect when unmounted', () => { + const testCollectElementProd = new CollectElement(id, + { + elementName, + rows, + }, + {}, + 'containerId', + true, + destroyCallback, + updateCallback, + { logLevel: LogLevel.ERROR, env: Env.PROD }); + let div = document.createElement('div') + div.setAttribute('id', 'id1') + document.body.appendChild(div); + testCollectElementProd.mount('#id1'); + + expect(ResizeObserver).toHaveBeenCalled(); + expect(testCollectElementProd.resizeObserver.observe).toHaveBeenCalledWith(document.querySelector('#id1')) + + testCollectElementProd.unmount(); + expect(ResizeObserver).toHaveBeenCalled(); + expect(testCollectElementProd.resizeObserver.disconnect).toHaveBeenCalled(); + }); // it('update element in DEV environment', () => { // const collectElement = new CollectElement(id, From f7cf33bf7ca4ab70ebdc9f46c339b32cd58d8d59 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 13 Dec 2023 12:16:11 +0000 Subject: [PATCH 16/32] [AUTOMATED] Private Release 1.33.4-dev.390d2fc --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d6d7bc43..9cbeba25 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.33.4-dev.04bbcfc", + "version": "1.33.4-dev.390d2fc", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From cdf9bf7d2f15342321c59f5e4355a004343e6854 Mon Sep 17 00:00:00 2001 From: yaswanth-pula-skyflow Date: Thu, 14 Dec 2023 13:19:22 +0000 Subject: [PATCH 17/32] [AUTOMATED] Public Release - 1.34.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9cbeba25..3003b47b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.33.4-dev.390d2fc", + "version": "1.34.0", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 5c941097a2639d17419e4f7f97cb69806e41ca57 Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Fri, 15 Dec 2023 10:52:07 +0530 Subject: [PATCH 18/32] SK-1263 README, CHANGELOG & Samples for update element props - SK-1263 Added CHANGELOG - SK-1263 Updated to latest SDK version in Samples - SK-1263 Updated README --- CHANGELOG.md | 12 ++++++++++++ README.md | 6 +++++- .../using-npm/skyflow-elements-update/package.json | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6254bd2e..c14b62fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## [1.34.0] - 2023-12-15 +### Added +- Ability to update Collect and Reveal element properties dynamically. + +### Fixed +- iFrame height resize issue +- ExpiryMonth Element's invalid input styles issue + +## [1.33.4] - 2023-12-07 +## Fixed +- jQuery dependency replacement changes. + ## [1.33.3] - 2023-11-15 ## Fixed - Patch fix for reverting changes for internal jQuery dependency changes. diff --git a/README.md b/README.md index c9e71cb7..62b9f129 100644 --- a/README.md +++ b/README.md @@ -1173,6 +1173,7 @@ const updateElement = { label: 'string', // Optional. Label for the form element. placeholder: 'string', // Optional. Placeholder for the form element. validations: [], // Optional. Array of validation rules. + skyflowID: 'string' // Optional. SkyflowID of the record. }; ``` @@ -2856,7 +2857,10 @@ const updateElement = { errorTextStyles: {}, // Optional, styles that will be applied to the errorText of the reveal element. label: 'string', // Optional, label for the form element. altText: 'string', // Optional, string that is shown before reveal, will show token if altText is not provided. - redaction: RedactionType, //Optional, Redaction Type to be applied to data. + redaction: RedactionType, // Optional, Redaction Type to be applied to data. + skyflowID: 'string', // Optional, SkyflowID of the record. + table: 'string', // Optional, table from which to render the file. + column: 'string' // Optional, column name from which to render the file. }; ``` diff --git a/samples/using-npm/skyflow-elements-update/package.json b/samples/using-npm/skyflow-elements-update/package.json index db063c2c..0a55566e 100644 --- a/samples/using-npm/skyflow-elements-update/package.json +++ b/samples/using-npm/skyflow-elements-update/package.json @@ -10,6 +10,6 @@ "author": "", "license": "ISC", "dependencies": { - "skyflow-js": "^1.33.0" + "skyflow-js": "^1.34.0" } } From 032765b642c80a36bb70e5189e4be84bb3de59f2 Mon Sep 17 00:00:00 2001 From: skyflow-vivek Date: Fri, 15 Dec 2023 19:19:02 +0530 Subject: [PATCH 19/32] SK-1263 Addresed comments on README --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 62b9f129..d381bd25 100644 --- a/README.md +++ b/README.md @@ -1231,9 +1231,9 @@ const cvvElement = collectContainer.create({ }); // Mount the collect elements. -collectContainer.mount('#cardHolderNameElement'); // Assumes there is a div with id='#cardHolderNameElement' in the webpage. -collectContainer.mount('#cardNumberElement'); // Assumes there is a div with id='#cardNumberElement' in the webpage. -collectContainer.mount('#cvvElement'); // Assumes there is a div with id='#cvvElement' in the webpage. +cardHolderNameElement.mount('#cardHolderNameElement'); // Assumes there is a div with id='#cardHolderNameElement' in the webpage. +cardNumberElement.mount('#cardNumberElement'); // Assumes there is a div with id='#cardNumberElement' in the webpage. +cvvElement.mount('#cvvElement'); // Assumes there is a div with id='#cvvElement' in the webpage. // ... @@ -2727,9 +2727,9 @@ const fileElement = { inputStyles: {}, // Optional, styles to be applied to the element. errorTextStyles: {}, // Optional, styles that will be applied to the errorText of the render element. altText: 'string', // Optional, string that is shown before file render call - skyflowID: 'string', // Required, skyflow id of the file to be render - column: 'string', // Required, column name of the file to be render - table: 'string', // Required, table name of the file to be render + skyflowID: 'string', // Required, skyflow id of the file to render + column: 'string', // Required, column name of the file to render + table: 'string', // Required, table name of the file to render }; ``` The inputStyles and errorTextStyles parameters accept a styles object as described in the [previous section](https://github.com/skyflowapi/skyflow-js#step-2-create-a-collect-element) for collecting data. But for render file elements, inputStyles accepts only base variant, global style objects. @@ -2858,9 +2858,9 @@ const updateElement = { label: 'string', // Optional, label for the form element. altText: 'string', // Optional, string that is shown before reveal, will show token if altText is not provided. redaction: RedactionType, // Optional, Redaction Type to be applied to data. - skyflowID: 'string', // Optional, SkyflowID of the record. - table: 'string', // Optional, table from which to render the file. - column: 'string' // Optional, column name from which to render the file. + skyflowID: 'string', // Optional, Skyflow ID of the file to render. + table: 'string', // Optional, table name of the file to render. + column: 'string' // Optional, column name of the file to render. }; ``` @@ -2892,7 +2892,7 @@ const stylesOptions = { }, }; -// Create collect elements +// Create reveal elements const cardHolderNameRevealElement = revealContainer.create({ token: 'ed5fdd1f-5009-435c-a06b-3417ce76d2c8', altText: 'first name', @@ -2908,9 +2908,9 @@ const cardNumberRevealElement = revealContainer.create({ redaction: 'RedactionType.CARD_NUMBER' }); -// Mount the collect elements. -revealContainer.mount('#cardHolderNameRevealElement'); // Assumes there is a div with id='#cardHolderNameRevealElement' in the webpage. -revealContainer.mount('#cardNumberRevealElement'); // Assumes there is a div with id='#cardNumberRevealElement' in the webpage. +// Mount the reveal elements. +cardHolderNameRevealElement.mount('#cardHolderNameRevealElement'); // Assumes there is a div with id='#cardHolderNameRevealElement' in the webpage. +cardNumberRevealElement.mount('#cardNumberRevealElement'); // Assumes there is a div with id='#cardNumberRevealElement' in the webpage. // ... From 9f09ed35c3f6bc1276c2957da6284e97b50e442a Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:58:07 +0530 Subject: [PATCH 20/32] Release/24.1.1 (#378) * SK-1343 name restrictions for file upload (#371) * SK-1343-name-restrictions-for-file-upload-in-js-sdk * SK-1343 updated regex * [AUTOMATED] Private Release 1.34.0-dev.4cac48a * SK-1343 updated logic and test cases * SK-1343 updated test cases * SK-1343 updated logic and test cases * [AUTOMATED] Private Release 1.34.0-dev.cef5ff0 * SK-896: configured custom error is overridden with field is required when using regex custom validations to validate for empty characters (#379) SK-896: configured custom error is overridden with field is required when using regex custom validations to validate for empty characters * [AUTOMATED] Private Release 1.34.0-dev.6478b34 --------- Co-authored-by: skyflow-bharti Co-authored-by: skyflow-amith <134510722+skyflow-amith@users.noreply.github.com> Co-authored-by: skyflow-amith --- package.json | 2 +- src/core/constants.ts | 2 + src/core/external/collect/collect-element.ts | 4 +- src/core/internal/frame-elements.ts | 1 + src/core/internal/iframe-form/index.ts | 67 +++-- src/core/internal/index.ts | 5 +- src/utils/constants.ts | 1 + src/utils/helpers/index.ts | 3 + src/utils/logs.ts | 2 + .../internal/iframe-form/iframe-form.test.js | 239 +++++++++++++++++- tests/utils/helpers.test.js | 44 +++- 11 files changed, 335 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 3003b47b..644837c8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.34.0", + "version": "1.34.0-dev.6478b34", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", diff --git a/src/core/constants.ts b/src/core/constants.ts index be4c3ef8..8fcb0701 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -431,6 +431,8 @@ export const COLLECT_ELEMENT_LABEL_DEFAULT_STYLES = { }, }; +export const ALLOWED_NAME_FOR_FILE = /^[a-zA-Z0-9!\-_.*()]+$/; + export const CARD_TYPE_REGEX = { [CardType.VISA]: { regex: /^4\d*/, diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 4bcc3cd1..6f9302bc 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -113,12 +113,12 @@ class CollectElement extends SkyflowElement { this.#states.push({ isEmpty: true, isComplete: false, - isValid: false, + isValid: (!element.required && this.#state.isEmpty), isFocused: false, value: this.#doesReturnValue ? '' : undefined, elementType: element.elementType, name: element.elementName, - isRequired: false, + isRequired: element.required, }); }); this.#iframe = new IFrame( diff --git a/src/core/internal/frame-elements.ts b/src/core/internal/frame-elements.ts index 748d9813..3d250136 100644 --- a/src/core/internal/frame-elements.ts +++ b/src/core/internal/frame-elements.ts @@ -227,6 +227,7 @@ export default class FrameElements { element.elementName, element.label, element.skyflowID, + element.required, ); elements[element.elementName] = new FrameElement( iFrameFormElement, diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 8e30d84e..71382e4e 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -53,7 +53,7 @@ import { ValidationRuleType, } from '../../../utils/common'; import { - fileValidation, formatFrameNameToId, getReturnValue, removeSpaces, + fileValidation, formatFrameNameToId, getReturnValue, removeSpaces, vaildateFileName, } from '../../../utils/helpers'; import { ContainerType } from '../../../skyflow'; @@ -70,6 +70,7 @@ export class IFrameFormElement extends EventEmitter { isComplete: false, name: '', isRequired: false, + isTouched: false, }; readonly fieldType: string; @@ -139,7 +140,7 @@ export class IFrameFormElement extends EventEmitter { this.metaData = metaData; this.context = context; - + this.state.isRequired = metaData.isRequired; this.collectBusEvents(); } @@ -170,6 +171,7 @@ export class IFrameFormElement extends EventEmitter { }; changeFocus = (focus: boolean) => { + this.state.isTouched = true; this.state.isFocused = focus; // this.sendChangeStatus(); // this.setValue(this.state.value, true); @@ -317,8 +319,7 @@ export class IFrameFormElement extends EventEmitter { : DEFAULT_ERROR_TEXT_ELEMENT_TYPES[this.fieldType]; } } - if (!this.state.isValid && this.state.isEmpty) { - this.state.isRequired = true; + if (!this.state.isValid && this.state.isEmpty && this.state.isRequired) { if (this.label) { this.errorText = `${parameterizedString(logs.errorLogs.REQUIRED_COLLECT_VALUE, this.label)}`; @@ -360,6 +361,7 @@ export class IFrameFormElement extends EventEmitter { isEmpty: this.state.isEmpty, isComplete: this.state.isComplete, isRequired: this.state.isRequired, + isTouched: this.state.isTouched, // Card Number should return 8 digit bin data value: this.state.value && getReturnValue(this.state.value, this.fieldType, @@ -368,6 +370,8 @@ export class IFrameFormElement extends EventEmitter { validator(value: any) { let resp = true; + let vaildateFileNames = true; + if (this.fieldType === ElementType.CARD_NUMBER && value) { if (this.regex) { resp = this.regex.test(value) @@ -386,24 +390,33 @@ export class IFrameFormElement extends EventEmitter { } catch (err) { resp = false; } + vaildateFileNames = vaildateFileName(value.name); } else { // eslint-disable-next-line no-lonely-if if (this.regex && value) { resp = this.regex.test(value); } } - if (!resp) { - if (this.label) { - this.errorText = `${parameterizedString( - logs.errorLogs.INVALID_COLLECT_VALUE_WITH_LABEL, - this.label, - )}`; - } else { + if (!resp || !vaildateFileNames) { + if (!resp) { + if (this.label) { + this.errorText = `${parameterizedString( + logs.errorLogs.INVALID_COLLECT_VALUE_WITH_LABEL, + this.label, + )}`; + } else { + this.errorText = this.containerType === ContainerType.COLLECT + ? logs.errorLogs.INVALID_COLLECT_VALUE + : DEFAULT_ERROR_TEXT_ELEMENT_TYPES[this.fieldType]; + } + return resp; + } + if (!vaildateFileNames) { this.errorText = this.containerType === ContainerType.COLLECT - ? logs.errorLogs.INVALID_COLLECT_VALUE + ? logs.errorLogs.INVALID_FILE_NAMES : DEFAULT_ERROR_TEXT_ELEMENT_TYPES[this.fieldType]; + return vaildateFileNames; } - return resp; } resp = this.validateCustomValidations(value); @@ -628,6 +641,7 @@ export class IFrameFormElement extends EventEmitter { isComplete: false, name: '', isRequired: false, + isTouched: false, }; } @@ -799,11 +813,20 @@ export class IFrameForm { this.context = context; } - private getOrCreateIFrameFormElement = (frameName, label, skyflowID) => { - this.iFrameFormElements[frameName] = this.iFrameFormElements[frameName] - || new IFrameFormElement(frameName, label, { - ...this.clientMetaData, - }, this.context, skyflowID); + private getOrCreateIFrameFormElement = (frameName, label, skyflowID, isRequired) => { + if (!this.iFrameFormElements[frameName]) { + if (isRequired) { + this.iFrameFormElements[frameName] = new IFrameFormElement(frameName, label, { + ...this.clientMetaData, + isRequired, + }, this.context, skyflowID); + } else { + this.iFrameFormElements[frameName] = new IFrameFormElement(frameName, label, { + ...this.clientMetaData, + isRequired: false, + }, this.context, skyflowID); + } + } return this.iFrameFormElements[frameName]; }; @@ -878,6 +901,12 @@ export class IFrameForm { formData.append(column, value); + const validateFileName = vaildateFileName(state.value.name); + + if (!validateFileName) { + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true)); + } + const { client } = this; const sendRequest = () => new Promise((rootResolve, rootReject) => { const clientId = client.toJSON()?.metaData?.uuid || ''; @@ -927,7 +956,7 @@ export class IFrameForm { state, doesClientHasError, clientErrorText, errorText, onFocusChange, } = this.iFrameFormElements[formElements[i]]; - if (state.isRequired) { + if (state.isRequired || !state.isValid) { onFocusChange(false); } diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 2f06966d..c34923b3 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -210,7 +210,7 @@ export class FrameElement { } this.iFrameFormElement.on(ELEMENT_EVENTS_TO_CLIENT.FOCUS, (state) => { this.focusChange(true); - state.isEmpty = false; + state.isEmpty = !state.value; // On Focus the error state should be false if (state.error && this.domError) { state.isValid = true; @@ -642,6 +642,7 @@ export class FrameElement { isEmpty: boolean; isComplete: boolean; value: string | Blob | undefined; + isTouched: boolean; }) { const classes: string[] = []; const labelClasses: string[] = []; @@ -650,7 +651,7 @@ export class FrameElement { classes.push(STYLE_TYPE.FOCUS); labelClasses.push(STYLE_TYPE.FOCUS); } - if (this.options.required && state.value === '' && !state.isFocused) { + if (state.isTouched && !state.isFocused && !state.isValid) { classes.push(STYLE_TYPE.INVALID); } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 32f16008..81cd0cf9 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -4,6 +4,7 @@ Copyright (c) 2022 Skyflow, Inc. import logs from './logs'; const SKYFLOW_ERROR_CODE = { + INVALID_FILE_NAME: { code: 400, description: logs.errorLogs.INVALID_FILE_NAME }, INVALID_FIELD: { code: 400, description: logs.errorLogs.INVALID_FIELD }, VAULTID_IS_REQUIRED: { code: 400, description: logs.errorLogs.VAULTID_IS_REQUIRED }, EMPTY_VAULTID_IN_INIT: { code: 400, description: logs.errorLogs.EMPTY_VAULTID_IN_INIT }, diff --git a/src/utils/helpers/index.ts b/src/utils/helpers/index.ts index 686d3927..a817a85e 100644 --- a/src/utils/helpers/index.ts +++ b/src/utils/helpers/index.ts @@ -3,6 +3,7 @@ Copyright (c) 2022 Skyflow, Inc. */ import { SdkInfo } from '../../client'; import { + ALLOWED_NAME_FOR_FILE, CardType, COPY_UTILS, DEFAULT_INPUT_FORMAT_TRANSLATION, ElementType, } from '../../core/constants'; @@ -183,6 +184,8 @@ export const fileValidation = (value, required: Boolean = false) => { return true; }; +export const vaildateFileName = (name) => ALLOWED_NAME_FOR_FILE.test(name); + export const styleToString = (style) => Object.keys(style).reduce((acc, key) => ( `${acc + key.split(/(?=[A-Z])/).join('-').toLowerCase()}:${style[key]};` ), ''); diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 13c245ce..c2643d33 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -87,6 +87,8 @@ const logs = { VALIDATE_GET_BY_ID_INPUT: '%s1 - Validating getByID input.', }, errorLogs: { + INVALID_FILE_NAMES: 'Invalid File Name. Only alphanumeric characters and !-_.*() are allowed.', + INVALID_FILE_NAME: 'Interface: collect element - Invalid File Name. Only alphanumeric characters and !-_.*() are allowed.', CLIENT_CONNECTION: 'Interface: collect container - client connection not established. client info has not reached iframes', INVALID_BEARER_TOKEN: 'Interface: init - Invalid token is generated from getBearerToken callback', BEARER_TOKEN_REJECTED: 'Interface: init - GetBearerToken promise got rejected.', diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index f26eadeb..0a47e7fa 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -75,6 +75,7 @@ describe('test iframeFormelement', () => { windowSpy.mockRestore(); }); + test('iframeFormelement constructor', () => { const element = new IFrameFormElement(collect_element, {}, context) expect(element.state.isFocused).toBe(false) @@ -213,26 +214,47 @@ describe('test iframeFormelement', () => { }); test('test error text without label composable element',()=>{ - const element = new IFrameFormElement(`element:CVV:${tableCol}`, '',{containerType:ContainerType.COMPOSABLE}, context) + const element = new IFrameFormElement(`element:CVV:${tableCol}`, '',{containerType:ContainerType.COMPOSABLE,isRequired:true}, context) element.setValidation(); element.doesClientHasError = true; element.setValue(''); expect(element.errorText).toBe('cvv is required'); }); test('test error text without label collect element',()=>{ - const element = new IFrameFormElement(`element:CVV:${tableCol}`, '',{containerType:ContainerType.COLLECT}, context) + const element = new IFrameFormElement(`element:CVV:${tableCol}`, '',{containerType:ContainerType.COLLECT,isRequired:true}, context) element.setValidation(); element.doesClientHasError = true; element.setValue(''); expect(element.errorText).toBe('Field is required'); }); test('test error text with label composable element',()=>{ - const element = new IFrameFormElement(`element:CVV:${tableCol}`, 'cvv label',{containerType:ContainerType.COMPOSABLE}, context) + const element = new IFrameFormElement(`element:CVV:${tableCol}`, 'cvv label',{containerType:ContainerType.COMPOSABLE,isRequired:true}, context) element.setValidation(); element.doesClientHasError = true; element.setValue(''); expect(element.errorText).toBe('cvv label is required'); }); + test('test error text without label composable element',()=>{ + const element = new IFrameFormElement(`element:CVV:${tableCol}`, '',{containerType:ContainerType.COMPOSABLE}, context) + element.setValidation(); + element.doesClientHasError = true; + element.setValue(''); + expect(element.errorText).toBe('Invalid cvv'); + }); + test('test error text without label collect element',()=>{ + const element = new IFrameFormElement(`element:CVV:${tableCol}`, '',{containerType:ContainerType.COLLECT}, context) + element.setValidation(); + element.doesClientHasError = true; + element.setValue(''); + expect(element.errorText).toBe('Invalid value'); + }); + test('test error text with label composable element',()=>{ + const element = new IFrameFormElement(`element:CVV:${tableCol}`, 'cvv label',{containerType:ContainerType.COMPOSABLE}, context) + element.setValidation(); + element.doesClientHasError = true; + element.setValue(''); + expect(element.errorText).toBe('Invalid cvv label'); + }); test('test card_number validations', () => { const element = new IFrameFormElement(`element:CARD_NUMBER:${tableCol}`, {}, context) element.setValidation() @@ -287,6 +309,37 @@ describe('test iframeFormelement', () => { expect(invalid).toBe(false) }) + + test('file_input validation file name', () => { + const invalidFile = { + lastModified: '', + lastModifiedDate: '', + name: "sample 1.png", + size: 48848, + type: "image/jpeg", + webkitRelativePath: "" + } + + const fileElement = new IFrameFormElement(file_element, {}, context) + fileElement.setValidation() + const invalid = fileElement.validator(invalidFile) + expect(invalid).toBe(false) + }) + test('file_input validation file name invalid case', () => { + const invalidFile = { + lastModified: '', + lastModifiedDate: '', + name: "sample 1.png", + size: 48848, + type: "image/png" , + webkitRelativePath: "" + } + const fileElement = new IFrameFormElement(file_element, {}, context) + fileElement.setValidation() + const invalid = fileElement.validator(invalidFile) + expect(invalid).toBe(false) + }) + test('invalid custom validations', () => { const element2 = new IFrameFormElement(`element:EXPIRATION_MONTH:${tableCol}`, {}, context) element2.setValidation([{type:'DEFAULT',params:{}}]) @@ -431,6 +484,15 @@ const fileData = { type: "application/pdf", webkitRelativePath: "" } +const fileDataInvalid = { + lastModified: '', + lastModifiedDate: '', + name: "sample.pdf", + size: 74570648, + type: "application/pdf", + webkitRelativePath: "" +} + export const insertResponse = { vaultID: 'vault123', @@ -534,6 +596,87 @@ describe('test iframeForm collect method', () => { frameReadyCb({ name:collect_element}) + const createFormElement = skyflowInit.mock.calls[0][0] + const element = createFormElement(collect_element,undefined,undefined,true) + + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); + const tokenizationCb = tokenizationEvent[0][1]; + const cb2 = jest.fn(); + tokenizationCb(data, cb2) + + setTimeout(() => { + expect(cb2.mock.calls[0][0].error.message).toBeDefined() + }, 1000) + + element.setValue('123') + const cb3 = jest.fn() + tokenizationCb(data, cb3) + + setTimeout(() => { + expect(cb3.mock.calls[0][0].records.length).toBe(2); + }, 1000) + + element.fieldName = 'col'; + element.tableName = 'table'; + element.state.name = 'col'; + + const cb4 = jest.fn() + tokenizationCb({ + ...data, + additionalFields: { + ...data.additionalFields, + records: [{ + table: 'table', + fields: { + col: '123' + } + }] + } + }, cb4) + + setTimeout(() => { + expect(cb4.mock.calls[0][0].error).toBeDefined() + done() + }, 1000) + + + }) + + test('initialize iframeform and submit collect with invalid input and not required field', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + form.setClient(clientObj) + form.setClientMetadata(metaData) + form.setContext(context) + + const frameReadyEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + 'controllerId'); + const frameReadyCb = frameReadyEvent[0][1]; + + expect(() => { frameReadyCb({}) }).toThrow(SkyflowError) + + frameReadyCb({ name: COLLECT_FRAME_CONTROLLER }) + + expect(() => { frameReadyCb({ name: "element:type:aW52YWxpZA==" }) }).toThrow(SkyflowError) + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + + frameReadyCb({ name:collect_element}) + const createFormElement = skyflowInit.mock.calls[0][0] const element = createFormElement(collect_element) @@ -771,7 +914,7 @@ describe('test file Upload method', () => { fileUploadCb(fileData, cb2) setTimeout(() => { - expect(cb2.mock.calls[0][0].error.message).toBeDefined() + expect(cb2.mock.calls[0][0].error).toBeDefined() }, 3000) fileElement.setValue({ @@ -784,11 +927,6 @@ describe('test file Upload method', () => { }) const cb3 = jest.fn() fileUploadCb(fileData, cb3) - - setTimeout(() => { - expect(cb3.mock.calls[0][0].records.length).toBe(1); - done() - }, 1000) }); test('validate for file input - valid allowedFileType array', () => { const elementType = ELEMENTS.FILE_INPUT.name; @@ -827,5 +965,86 @@ describe('test file Upload method', () => { const formattedOptions = formatOptions(elementType, options, logLevel); }).toThrowError(logs.errorLogs.EMPTY_ALLOWED_OPTIONS_ARRAY); }); - + test('initialize iFrame and upload with file input error case', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + form.setClient(fileClientObj) + form.setClientMetadata(metaData) + form.setContext(context) + + const frameReadyEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + 'controllerId'); + const frameReadyCb = frameReadyEvent[0][1]; + + expect(() => { frameReadyCb({}) }).toThrow(SkyflowError) + + frameReadyCb({ name: COLLECT_FRAME_CONTROLLER }) + + expect(() => { frameReadyCb({ name: "element:type:aW52YWxpZA==" }) }).toThrow(SkyflowError) + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: file_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + + frameReadyCb({ name: file_element }) + + const createFormElement = skyflowInit.mock.calls[0][0] + const fileElement = createFormElement(file_element) + + const fileUploadEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.FILE_UPLOAD + 'controllerId'); + const fileUploadCb = fileUploadEvent[0][1]; + const cb2 = jest.fn(); + fileUploadCb(fileData, cb2) + + setTimeout(() => { + console.log('cb2.mock.calls', cb2.mock.calls); + expect(cb2.mock.calls[0][0].error).toBeDefined() + done() + }, 3000) + + fileElement.setValue({ + lastModified: '', + lastModifiedDate: '', + name: "sample .jpg", + size: 48848, + type: "image/jpeg", + webkitRelativePath: "" + }) + const cb3 = jest.fn() + fileUploadCb(fileData, cb3) + + setTimeout(() => { + console.log('cb3.mock.calls', cb3.mock.calls); + expect(cb3.mock.calls[0][0].error).toBeDefined() + done() + }, 1000) + fileElement.setValue({ + lastModified: '', + lastModifiedDate: '', + name: "sample.zip", + size: 48848, + type: "application/zip", + webkitRelativePath: "" + }) + const cb4 = jest.fn() + fileUploadCb(fileData, cb4) + + setTimeout(() => { + expect(cb4.mock.calls[0][0].error).toBeDefined() + done() + }, 1000) + }); }) \ No newline at end of file diff --git a/tests/utils/helpers.test.js b/tests/utils/helpers.test.js index 413ffaeb..59724c76 100644 --- a/tests/utils/helpers.test.js +++ b/tests/utils/helpers.test.js @@ -22,7 +22,8 @@ import { getSdkVersionName, getMetaObject, checkAndSetForCustomUrl, - domReady + domReady, + vaildateFileName } from '../../src/utils/helpers/index'; import { parameterizedString @@ -108,6 +109,47 @@ describe('test copy feature', () => { }) }) +describe('test file name validation', () => { + + test('invalid file name', () => { + const invalidFileName = [ { + lastModified: '', + lastModifiedDate: '', + name: "sample .zip", + size: 48848, + type: "application/zip", + webkitRelativePath: "" + },{ + lastModified: '', + lastModifiedDate: '', + name: "sample%.deb", + size: 48848, + type: "application/vnd.debian.binary-package", + webkitRelativePath: "" + } + ] + invalidFileName.forEach(invalidFile => { + try { + vaildateFileName(invalidFile) + } catch(err) { + expect(err?.error?.description).toEqual(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME.description) + } + }) + }) + test('valid file name', () => { + const file = { + lastModified: '', + lastModifiedDate: '', + name: "sample.jpg", + size: 48848, + type: "image/jpeg", + webkitRelativePath: "" + } + expect(vaildateFileName(file.name)).toBe(true); + }) + + }); + describe('test file validation', () => { test('invalid file type', () => { const invalidFiles = [ From b41e38b4343579aa9fdbc44018d49bff9f7acab6 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 10 Jan 2024 12:36:36 +0000 Subject: [PATCH 21/32] [AUTOMATED] Public Release - 1.34.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 644837c8..3c52c30b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "1.34.0-dev.6478b34", + "version": "1.34.1", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From cfb14c1ef82dbe9803c0b2a574b4e76bcef1c1fe Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Tue, 16 Jan 2024 13:20:11 +0530 Subject: [PATCH 22/32] SK-1395 : Update tokenize function to use DOM traversal --- src/core/internal/iframe-form/index.ts | 130 ++++++++++++++----------- src/core/internal/index.ts | 6 +- src/utils/common/index.ts | 10 ++ 3 files changed, 86 insertions(+), 60 deletions(-) diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 71382e4e..5544ae4a 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -51,6 +51,7 @@ import { LogLevel, MessageType, ValidationRuleType, + ElementState, } from '../../../utils/common'; import { fileValidation, formatFrameNameToId, getReturnValue, removeSpaces, vaildateFileName, @@ -952,18 +953,22 @@ export class IFrameForm { this.iFrameFormElements[formElements[i]].fieldType !== ELEMENTS.FILE_INPUT.name ) { - const { - state, doesClientHasError, clientErrorText, errorText, onFocusChange, - } = this.iFrameFormElements[formElements[i]]; - - if (state.isRequired || !state.isValid) { - onFocusChange(false); - } - - if (!state.isValid || !state.isComplete) { - if (doesClientHasError) { - errorMessage += `${state.name}:${clientErrorText}`; - } else { errorMessage += `${state.name}:${errorText} `; } + const Frame = window.parent.frames[`${this.iFrameFormElements[formElements[i]].iFrameName}:${this.controllerId}:${this.logLevel}`]; + const inputElement = Frame.document + .getElementById(this.iFrameFormElements[formElements[i]].iFrameName); + if (inputElement) { + const state = inputElement.state; + const { + doesClientHasError, clientErrorText, errorText, onFocusChange, + } = this.iFrameFormElements[formElements[i]]; + if (state.isRequired) { + onFocusChange(false); + } + if (!state.isValid || !state.isComplete) { + if (doesClientHasError) { + errorMessage += `${state.name}:${clientErrorText}`; + } else { errorMessage += `${state.name}:${errorText} `; } + } } } } @@ -973,67 +978,74 @@ export class IFrameForm { } for (let i = 0; i < formElements.length; i += 1) { - const { - state, tableName, validations, skyflowID, - } = this.iFrameFormElements[formElements[i]]; - if (tableName) { - if ( - this.iFrameFormElements[formElements[i]].fieldType - !== ELEMENTS.FILE_INPUT.name - ) { + const Frame = window.parent.frames[`${this.iFrameFormElements[formElements[i]].iFrameName}:${this.controllerId}:${this.logLevel}`]; + const inputElement = Frame.document + .getElementById(this.iFrameFormElements[formElements[i]].iFrameName) as HTMLElement + & { state : ElementState }; + if (inputElement) { + const state = inputElement.state; + const { + tableName, validations, skyflowID, + } = this.iFrameFormElements[formElements[i]]; + if (tableName) { if ( this.iFrameFormElements[formElements[i]].fieldType - === ELEMENTS.checkbox.name + !== ELEMENTS.FILE_INPUT.name ) { - if (insertResponseObject[state.name]) { - insertResponseObject[state.name] = `${insertResponseObject[state.name]},${state.value - }`; - } else { - insertResponseObject[state.name] = state.value; - } - } else if (insertResponseObject[tableName] && !(skyflowID === '') && skyflowID === undefined) { - if (get(insertResponseObject[tableName], state.name) + if ( + this.iFrameFormElements[formElements[i]].fieldType + === ELEMENTS.checkbox.name + ) { + if (insertResponseObject[state.name]) { + insertResponseObject[state.name] = `${insertResponseObject[state.name]},${state.value + }`; + } else { + insertResponseObject[state.name] = state.value; + } + } else if (insertResponseObject[tableName] && !(skyflowID === '') && skyflowID === undefined) { + if (get(insertResponseObject[tableName], state.name) && !(validations && checkForElementMatchRule(validations))) { - return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.DUPLICATE_ELEMENT, - [state.name, tableName], true)); - } - set( - insertResponseObject[tableName], - state.name, - this.iFrameFormElements[formElements[i]].getUnformattedValue(), - ); - } else if (skyflowID || skyflowID === '') { - if (skyflowID === '' || skyflowID === null) { - return Promise.reject(new SkyflowError( - SKYFLOW_ERROR_CODE.EMPTY_SKYFLOW_ID_IN_ADDITIONAL_FIELDS, - )); - } - if (updateResponseObject[skyflowID]) { + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.DUPLICATE_ELEMENT, + [state.name, tableName], true)); + } set( - updateResponseObject[skyflowID], + insertResponseObject[tableName], state.name, this.iFrameFormElements[formElements[i]].getUnformattedValue(), ); + } else if (skyflowID || skyflowID === '') { + if (skyflowID === '' || skyflowID === null) { + return Promise.reject(new SkyflowError( + SKYFLOW_ERROR_CODE.EMPTY_SKYFLOW_ID_IN_ADDITIONAL_FIELDS, + )); + } + if (updateResponseObject[skyflowID]) { + set( + updateResponseObject[skyflowID], + state.name, + this.iFrameFormElements[formElements[i]].getUnformattedValue(), + ); + } else { + updateResponseObject[skyflowID] = {}; + set( + updateResponseObject[skyflowID], + state.name, + this.iFrameFormElements[formElements[i]].getUnformattedValue(), + ); + set( + updateResponseObject[skyflowID], + 'table', + tableName, + ); + } } else { - updateResponseObject[skyflowID] = {}; + insertResponseObject[tableName] = {}; set( - updateResponseObject[skyflowID], + insertResponseObject[tableName], state.name, this.iFrameFormElements[formElements[i]].getUnformattedValue(), ); - set( - updateResponseObject[skyflowID], - 'table', - tableName, - ); } - } else { - insertResponseObject[tableName] = {}; - set( - insertResponseObject[tableName], - state.name, - this.iFrameFormElements[formElements[i]].getUnformattedValue(), - ); } } } diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index c34923b3..934b233a 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -106,7 +106,8 @@ export class FrameElement { private domLabel?: HTMLLabelElement; - private domInput?: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFormElement; + private domInput?: (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFormElement) + & { state:ElementState }; public domError?: HTMLSpanElement; @@ -167,6 +168,9 @@ export class FrameElement { const inputElement = document.createElement(type); this.domInput = inputElement; + if (this.domInput) { + this.domInput.state = this.iFrameFormElement.state; + } inputElement.setAttribute(CUSTOM_ROW_ID_ATTRIBUTE, this.htmlDivElement?.id?.split(':')[0] || ''); this.inputParent.append(inputElement); diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 77bca655..40b763dc 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -149,3 +149,13 @@ export interface IDeleteResponseType { records?: Record[]; errors?: Record[]; } + +export interface ElementState { + value: any | string | undefined; + isFocused: boolean, + isValid: boolean, + isEmpty: boolean, + isComplete: boolean, + name: string, + isRequired: boolean, +} From 765e7b67ee51cfb282535314fed46c10df407125 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Tue, 16 Jan 2024 13:59:50 +0530 Subject: [PATCH 23/32] SK-1395 : interface import fix --- src/core/internal/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 934b233a..3b71b22b 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -33,7 +33,7 @@ import getCssClassesFromJss, { generateCssWithoutClass } from '../../libs/jss-st import { parameterizedString, printLog } from '../../utils/logs-helper'; import logs from '../../utils/logs'; import { detectCardType } from '../../utils/validators'; -import { LogLevel, MessageType } from '../../utils/common'; +import { LogLevel, MessageType, ElementState } from '../../utils/common'; import { addSeperatorToCardNumberMask, appendMonthFourDigitYears, From 6382b6d5ff858a9c89051577dc27a9e2326e871f Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Thu, 18 Jan 2024 11:24:02 +0530 Subject: [PATCH 24/32] SK-1409 : Update tokenize function to remove dependency from iFrameFormElements --- src/core/internal/iframe-form/index.ts | 14 +++++--------- src/core/internal/index.ts | 6 +++--- src/utils/common/index.ts | 10 ---------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 5544ae4a..04e18a8c 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -51,7 +51,6 @@ import { LogLevel, MessageType, ValidationRuleType, - ElementState, } from '../../../utils/common'; import { fileValidation, formatFrameNameToId, getReturnValue, removeSpaces, vaildateFileName, @@ -957,10 +956,9 @@ export class IFrameForm { const inputElement = Frame.document .getElementById(this.iFrameFormElements[formElements[i]].iFrameName); if (inputElement) { - const state = inputElement.state; const { - doesClientHasError, clientErrorText, errorText, onFocusChange, - } = this.iFrameFormElements[formElements[i]]; + state, doesClientHasError, clientErrorText, errorText, onFocusChange, + } = inputElement.iFrameFormElement; if (state.isRequired) { onFocusChange(false); } @@ -980,13 +978,11 @@ export class IFrameForm { for (let i = 0; i < formElements.length; i += 1) { const Frame = window.parent.frames[`${this.iFrameFormElements[formElements[i]].iFrameName}:${this.controllerId}:${this.logLevel}`]; const inputElement = Frame.document - .getElementById(this.iFrameFormElements[formElements[i]].iFrameName) as HTMLElement - & { state : ElementState }; + .getElementById(this.iFrameFormElements[formElements[i]].iFrameName); if (inputElement) { - const state = inputElement.state; const { - tableName, validations, skyflowID, - } = this.iFrameFormElements[formElements[i]]; + state, tableName, validations, skyflowID, + } = inputElement.iFrameFormElement; if (tableName) { if ( this.iFrameFormElements[formElements[i]].fieldType diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 3b71b22b..9537699e 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -33,7 +33,7 @@ import getCssClassesFromJss, { generateCssWithoutClass } from '../../libs/jss-st import { parameterizedString, printLog } from '../../utils/logs-helper'; import logs from '../../utils/logs'; import { detectCardType } from '../../utils/validators'; -import { LogLevel, MessageType, ElementState } from '../../utils/common'; +import { LogLevel, MessageType } from '../../utils/common'; import { addSeperatorToCardNumberMask, appendMonthFourDigitYears, @@ -107,7 +107,7 @@ export class FrameElement { private domLabel?: HTMLLabelElement; private domInput?: (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFormElement) - & { state:ElementState }; + & { iFrameFormElement : IFrameFormElement }; public domError?: HTMLSpanElement; @@ -169,7 +169,7 @@ export class FrameElement { const inputElement = document.createElement(type); this.domInput = inputElement; if (this.domInput) { - this.domInput.state = this.iFrameFormElement.state; + this.domInput.iFrameFormElement = this.iFrameFormElement; } inputElement.setAttribute(CUSTOM_ROW_ID_ATTRIBUTE, this.htmlDivElement?.id?.split(':')[0] || ''); this.inputParent.append(inputElement); diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 40b763dc..77bca655 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -149,13 +149,3 @@ export interface IDeleteResponseType { records?: Record[]; errors?: Record[]; } - -export interface ElementState { - value: any | string | undefined; - isFocused: boolean, - isValid: boolean, - isEmpty: boolean, - isComplete: boolean, - name: string, - isRequired: boolean, -} From e1e38651209d654c61bb8e085f974b3406fc3a48 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Thu, 18 Jan 2024 22:38:39 +0530 Subject: [PATCH 25/32] SK-1409: Pass element names to tokenize function as a parameter --- .../external/collect/collect-container.ts | 3 ++ .../collect/compose-collect-container.ts | 9 ++++- src/core/internal/iframe-form/index.ts | 37 +++++++++---------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/core/external/collect/collect-container.ts b/src/core/external/collect/collect-container.ts index ea9d0c56..6647302f 100644 --- a/src/core/external/collect/collect-container.ts +++ b/src/core/external/collect/collect-container.ts @@ -270,6 +270,8 @@ class CollectContainer extends Container { try { validateInitConfig(this.#metaData.clientJSON.config); const collectElements = Object.values(this.#elements); + const elementIds = Object.keys(this.#elements) + .map((element) => ({ frameId: element, elementId: element })); collectElements.forEach((element) => { if (!element.isMounted()) { throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); @@ -292,6 +294,7 @@ class CollectContainer extends Container { { ...options, tokens: options?.tokens !== undefined ? options.tokens : true, + elementIds, }, (data: any) => { if (!data || data?.error) { diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index f89cc580..f4722507 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -353,7 +353,7 @@ class ComposableContainer extends Container { throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); } }); - + const elementIds:{ frameId:string, elementId:string }[] = []; const collectElements = Object.values(this.#elements); collectElements.forEach((element) => { element.isValidElement(); @@ -368,6 +368,12 @@ class ComposableContainer extends Container { if (options?.upsert) { validateUpsertOptions(options?.upsert); } + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: this.#tempElements.elementName, + elementId: element.elementName, + }); + }); bus // .target(properties.IFRAME_SECURE_ORGIN) .emit( @@ -375,6 +381,7 @@ class ComposableContainer extends Container { { ...options, tokens: options?.tokens !== undefined ? options.tokens : true, + elementIds, }, (data: any) => { if (!data || data?.error) { diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 04e18a8c..02c9dc2d 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -945,17 +945,16 @@ export class IFrameForm { if (!this.client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); const insertResponseObject: any = {}; const updateResponseObject: any = {}; - const formElements = Object.keys(this.iFrameFormElements); let errorMessage = ''; - for (let i = 0; i < formElements.length; i += 1) { - if ( - this.iFrameFormElements[formElements[i]].fieldType - !== ELEMENTS.FILE_INPUT.name - ) { - const Frame = window.parent.frames[`${this.iFrameFormElements[formElements[i]].iFrameName}:${this.controllerId}:${this.logLevel}`]; - const inputElement = Frame.document - .getElementById(this.iFrameFormElements[formElements[i]].iFrameName); - if (inputElement) { + for (let i = 0; i < options.elementIds.length; i += 1) { + const Frame = window.parent.frames[`${options.elementIds[i].frameId}:${this.controllerId}:${this.logLevel}`]; + const inputElement = Frame.document + .getElementById(options.elementIds[i].elementId); + if (inputElement) { + if ( + inputElement.iFrameFormElement.fieldType + !== ELEMENTS.FILE_INPUT.name + ) { const { state, doesClientHasError, clientErrorText, errorText, onFocusChange, } = inputElement.iFrameFormElement; @@ -975,21 +974,21 @@ export class IFrameForm { return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.COMPLETE_AND_VALID_INPUTS, [`${errorMessage}`], true)); } - for (let i = 0; i < formElements.length; i += 1) { - const Frame = window.parent.frames[`${this.iFrameFormElements[formElements[i]].iFrameName}:${this.controllerId}:${this.logLevel}`]; + for (let i = 0; i < options.elementIds.length; i += 1) { + const Frame = window.parent.frames[`${options.elementIds[i].frameId}:${this.controllerId}:${this.logLevel}`]; const inputElement = Frame.document - .getElementById(this.iFrameFormElements[formElements[i]].iFrameName); + .getElementById(options.elementIds[i].elementId); if (inputElement) { const { state, tableName, validations, skyflowID, } = inputElement.iFrameFormElement; if (tableName) { if ( - this.iFrameFormElements[formElements[i]].fieldType + inputElement.iFrameFormElement.fieldType !== ELEMENTS.FILE_INPUT.name ) { if ( - this.iFrameFormElements[formElements[i]].fieldType + inputElement.iFrameFormElement.fieldType === ELEMENTS.checkbox.name ) { if (insertResponseObject[state.name]) { @@ -1007,7 +1006,7 @@ export class IFrameForm { set( insertResponseObject[tableName], state.name, - this.iFrameFormElements[formElements[i]].getUnformattedValue(), + inputElement.iFrameFormElement.getUnformattedValue(), ); } else if (skyflowID || skyflowID === '') { if (skyflowID === '' || skyflowID === null) { @@ -1019,14 +1018,14 @@ export class IFrameForm { set( updateResponseObject[skyflowID], state.name, - this.iFrameFormElements[formElements[i]].getUnformattedValue(), + inputElement.iFrameFormElement.getUnformattedValue(), ); } else { updateResponseObject[skyflowID] = {}; set( updateResponseObject[skyflowID], state.name, - this.iFrameFormElements[formElements[i]].getUnformattedValue(), + inputElement.iFrameFormElement.getUnformattedValue(), ); set( updateResponseObject[skyflowID], @@ -1039,7 +1038,7 @@ export class IFrameForm { set( insertResponseObject[tableName], state.name, - this.iFrameFormElements[formElements[i]].getUnformattedValue(), + inputElement.iFrameFormElement.getUnformattedValue(), ); } } From 1aa3dcd0ade821e112f049fa712ac0a26286ce12 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Fri, 19 Jan 2024 13:22:54 +0530 Subject: [PATCH 26/32] SK-1409: Fixed jest test cases --- .../internal/iframe-form/iframe-form.test.js | 336 +++++++++++++++++- 1 file changed, 318 insertions(+), 18 deletions(-) diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index 0a47e7fa..e8e89291 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -144,11 +144,22 @@ describe('test iframeFormelement', () => { }) test('test tokenize error case', () => { const element = new IFrameFormElement(`element:EXPIRATION_MONTH:${tableCol}`, {}, context) - const form = new IFrameForm(element) + const form = new IFrameForm("controllerId","",'ERROR') + jest.spyOn(window,'parent','get').mockImplementation(()=>({ + frames:{ + [`element:EXPIRATION_MONTH:${tableCol}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + })) form.setClient(clientObj1) form.setClientMetadata(metaData) form.setContext(context) - expect(form.tokenize()).rejects.toThrow(SkyflowError) + expect(form.tokenize({elementIds:[{elemenId:`element:EXPIRATION_MONTH:${tableCol}`,frameId:`element:EXPIRATION_MONTH:${tableCol}`}]})).rejects.toThrow(SkyflowError) }) test('test setValue for expiration_date', () => { @@ -602,7 +613,23 @@ describe('test iframeForm collect method', () => { const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data, cb2) + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2) setTimeout(() => { expect(cb2.mock.calls[0][0].error.message).toBeDefined() @@ -610,7 +637,7 @@ describe('test iframeForm collect method', () => { element.setValue('123') const cb3 = jest.fn() - tokenizationCb(data, cb3) + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb3) setTimeout(() => { expect(cb3.mock.calls[0][0].records.length).toBe(2); @@ -631,7 +658,8 @@ describe('test iframeForm collect method', () => { col: '123' } }] - } + }, + elementIds:[{elementId:collect_element,frameId:collect_element}] }, cb4) setTimeout(() => { @@ -639,6 +667,22 @@ describe('test iframeForm collect method', () => { done() }, 1000) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) @@ -683,7 +727,23 @@ describe('test iframeForm collect method', () => { const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data, cb2) + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2) setTimeout(() => { expect(cb2.mock.calls[0][0].error.message).toBeDefined() @@ -691,7 +751,7 @@ describe('test iframeForm collect method', () => { element.setValue('123') const cb3 = jest.fn() - tokenizationCb(data, cb3) + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb3) setTimeout(() => { expect(cb3.mock.calls[0][0].records.length).toBe(2); @@ -712,7 +772,8 @@ describe('test iframeForm collect method', () => { col: '123' } }] - } + }, + elementIds:[{elementId:collect_element,frameId:collect_element}] }, cb4) setTimeout(() => { @@ -721,6 +782,22 @@ describe('test iframeForm collect method', () => { }, 1000) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) test('collect submit without client initializatiob', () => { @@ -745,26 +822,96 @@ describe('test iframeForm collect method', () => { }) test('collect error second case', () => { const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(collect_element,"",{},context) + element.setValue("123") + let windowSpy = jest.spyOn(global, 'window', 'get'); + const skyflowInit = jest.fn(); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); form.setContext(context) form.setClient(clientObj1) - expect(form.tokenize(records)).rejects.toMatchObject({"error": {"code": 404, "description": "Not Found"}}); - }) + expect(form.tokenize({...records,elementIds:[{elementId:collect_element,frameId:collect_element}]})).rejects.toMatchObject({"error": {"code": 404, "description": "Not Found"}}); + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + }));}) test('insert records with tokens as true', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(collect_element,"",{},context) form.setClient(clientObj) form.setClientMetadata(metaData) form.setContext(context) - + element.setValue("123") + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data, cb2); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2); setTimeout(() => { expect(cb2.mock.calls[0][0].records.length).toBe(2); expect(cb2.mock.calls[0][0].records[0].table).toBe('table'); expect(Object.keys(cb2.mock.calls[0][0].records[0].fields).length).toBe(2); done() }, 1000) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) let clientObj1 = { config: {}, @@ -778,24 +925,83 @@ describe('test iframeForm collect method', () => { } test('ererr', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(collect_element,"",{},context) form.setClient(clientObj1) form.setClientMetadata(metaData) form.setContext(context) + element.setValue("123") + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data, cb2); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2); setTimeout(() => { expect(cb2.mock.calls[0][0].error).toBeDefined(); done() }, 1000) - form.tokenize(data).then().catch( err => { + form.tokenize({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}).then().catch( err => { expect(err).toBeDefined(); }) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) test('success', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(collect_element,"",{},context) + + element.setValue("123") + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + form.setClient(clientObj1) form.setClientMetadata(metaData) form.setContext(context) @@ -803,14 +1009,30 @@ describe('test iframeForm collect method', () => { const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data, cb2); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2); setTimeout(() => { expect(cb2.mock.calls[0][0].error).toBeDefined(); done() }, 1000) - form.tokenize(data).then().catch( err => { + form.tokenize({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}).then().catch( err => { expect(err).toBeDefined(); }) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) test('insert records duplicate error', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); @@ -818,6 +1040,27 @@ describe('test iframeForm collect method', () => { form.setClientMetadata(metaData) form.setContext(context) + const element = new IFrameFormElement(collect_element,"",{},context) + + element.setValue("123") + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); @@ -830,12 +1073,29 @@ describe('test iframeForm collect method', () => { col: '123', } }] - } + }, + elementIds:[{elementId:collect_element,frameId:collect_element}] }, cb2) setTimeout(() => { expect(cb2.mock.calls[0][0].error.message).toBeDefined() done() }, 1000) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) test('insert records with tokens as false', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); @@ -843,15 +1103,55 @@ describe('test iframeForm collect method', () => { form.setClientMetadata(metaData) form.setContext(context) + const element = new IFrameFormElement(collect_element,"",{},context) + + element.setValue("123") + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data2, cb2); + tokenizationCb({...data2, + elementIds:[{elementId:collect_element,frameId:collect_element}] + }, cb2); setTimeout(() => { expect(cb2.mock.calls[0][0].records[0].table).toBe('pii_fields'); expect(Object.keys(cb2.mock.calls[0][0].records[0]).length).toBe(2); done() }, 1000) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) }) From c9dcbdd15db70e12a242ed4f50e53db55d1c75e9 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Mon, 22 Jan 2024 14:44:42 +0530 Subject: [PATCH 27/32] SK-1395 : Test Cases update --- .../collect/composable-container.test.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/core/external/collect/composable-container.test.js b/tests/core/external/collect/composable-container.test.js index 12e44a05..b0b63ebd 100644 --- a/tests/core/external/collect/composable-container.test.js +++ b/tests/core/external/collect/composable-container.test.js @@ -28,13 +28,17 @@ jest.mock('../../../../src/libs/uuid', () => ({ const mockUnmount = jest.fn(); const updateMock = jest.fn(); jest.mock('../../../../src/core/external/collect/collect-element'); -CollectElement.mockImplementation(()=>({ +CollectElement.mockImplementation((_,tempElements)=>{ + tempElements.rows[0].elements.forEach((element)=>{ + element.isMounted = true; + }) + return { isMounted : ()=>(true), mount: jest.fn(), isValidElement: ()=>(true), unmount:mockUnmount, updateElement:updateMock -})) +}}) jest.mock('../../../../src/event-emitter'); const emitMock = jest.fn(); @@ -177,6 +181,10 @@ describe('test composable container class',()=>{ }); it('test collect',()=>{ + let readyCb; + on.mockImplementation((name,cb)=>{ + readyCb = cb; + }) const div = document.createElement('div'); div.id = 'composable' document.body.append(div); @@ -184,9 +192,13 @@ describe('test composable container class',()=>{ const element1 = container.create(cvvElement); const element2 = container.create(cardNumberElement); emitterSpy(); + readyCb({name:`${COLLECT_FRAME_CONTROLLER}1234`},jest.fn()) + container.mount('#composable'); container.collect(); + + on.mockImplementation((name,cb)=>{emitterSpy = cb}) }); it('test collect without mounting the container',(done)=>{ From e0d95ade1c13068481638aef8b0e772b6589d4e7 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Mon, 22 Jan 2024 14:58:34 +0530 Subject: [PATCH 28/32] SK-1395 : Test Cases update --- .../internal/iframe-form/iframe-form.test.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index e8e89291..c3b34ad3 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -978,6 +978,59 @@ describe('test iframeForm collect method', () => { } })); }) + test('inputElementNotFound', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(collect_element,"",{},context) + form.setClient(clientObj1) + form.setClientMetadata(metaData) + form.setContext(context) + + element.setValue("123") + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>(undefined) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); + const tokenizationCb = tokenizationEvent[0][1]; + const cb2 = jest.fn(); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2); + setTimeout(() => { + expect(cb2.mock.calls[0][0].error).toBeDefined(); + done() + }, 1000) + form.tokenize({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}).then().catch( err => { + expect(err).toBeDefined(); + }) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + }) test('success', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); const element = new IFrameFormElement(collect_element,"",{},context) From c327d2a791e164054bca393094f88224ed6a5d43 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Mon, 22 Jan 2024 15:35:38 +0530 Subject: [PATCH 29/32] SK-1409 : Coverage fixed --- src/core/internal/index.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 9537699e..737fb7cf 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -107,7 +107,7 @@ export class FrameElement { private domLabel?: HTMLLabelElement; private domInput?: (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFormElement) - & { iFrameFormElement : IFrameFormElement }; + & { iFrameFormElement? : IFrameFormElement }; public domError?: HTMLSpanElement; @@ -154,7 +154,7 @@ export class FrameElement { this.domError = document.createElement('span'); - let type; + let type:'select' | 'textarea' | 'input' = 'input'; if (this.iFrameFormElement?.fieldType === ELEMENTS.dropdown.name) { type = 'select'; } else if (this.iFrameFormElement?.fieldType === ELEMENTS.textarea.name) { @@ -168,9 +168,7 @@ export class FrameElement { const inputElement = document.createElement(type); this.domInput = inputElement; - if (this.domInput) { - this.domInput.iFrameFormElement = this.iFrameFormElement; - } + this.domInput.iFrameFormElement = this.iFrameFormElement; inputElement.setAttribute(CUSTOM_ROW_ID_ATTRIBUTE, this.htmlDivElement?.id?.split(':')[0] || ''); this.inputParent.append(inputElement); From 8624fa5560e13680d7ca89c660856c7a5269bc7c Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Tue, 23 Jan 2024 11:44:36 +0530 Subject: [PATCH 30/32] SK-1395: Test cases fixed --- .../internal/iframe-form/iframe-form.test.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index 94969227..6da537ad 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -613,7 +613,24 @@ describe('test iframeForm collect method', () => { const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); const tokenizationCb = tokenizationEvent[0][1]; const cb2 = jest.fn(); - tokenizationCb(data, cb2) + + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2) setTimeout(() => { expect(cb2.mock.calls[0][0].error.message).toBeDefined() @@ -621,7 +638,7 @@ describe('test iframeForm collect method', () => { element.setValue('123') const cb3 = jest.fn() - tokenizationCb(data, cb3) + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb3) setTimeout(() => { expect(cb3.mock.calls[0][0].records.length).toBe(2); @@ -642,7 +659,8 @@ describe('test iframeForm collect method', () => { col: '123' } }] - } + }, + elementIds:[{elementId:collect_element,frameId:collect_element}] }, cb4) setTimeout(() => { @@ -650,7 +668,22 @@ describe('test iframeForm collect method', () => { done() }, 1000) - + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); }) test('initialize iframeform and submit collect with invalid input and not required field', (done) => { From 23232cebeea4c42e5df78adae88307ab9d3a8e1a Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Tue, 23 Jan 2024 12:36:45 +0530 Subject: [PATCH 31/32] SK-1395 : Test cases coverage increased --- tests/core/internal/frame-controller.test.js | 127 ++++++++++++++++++ tests/core/internal/frame-elements.test.js | 45 ++++++- .../core/internal/reveal/reveal-frame.test.js | 28 ++++ 3 files changed, 198 insertions(+), 2 deletions(-) diff --git a/tests/core/internal/frame-controller.test.js b/tests/core/internal/frame-controller.test.js index f0eb1f60..85d6135d 100644 --- a/tests/core/internal/frame-controller.test.js +++ b/tests/core/internal/frame-controller.test.js @@ -34,6 +34,8 @@ const on = jest.fn(); const tableCol = btoa('table.col'); const collect_element = `element:CVV:${tableCol}`; +const collect_element_textarea = `element:${ELEMENTS.textarea.name}:${tableCol}`; +const collect_element_dropdown = `element:${ELEMENTS.dropdown.name}:${tableCol}`; const context = { logLevel: LogLevel.ERROR, @@ -220,6 +222,131 @@ describe('test frame controller', () => { element.setupInputField(); }); + test('FrameElement constructor : textarea', () => { + const div = document.createElement('div'); + + const formElement = new IFrameFormElement(collect_element_textarea, {}, context); + const element = new FrameElement(formElement, { + label: 'label', + inputStyles, + labelStyles, + errorTextStyles, + }, div); + + const inst = EventEmitter.mock.instances[0]; + const onSpy = inst.on.mock.calls; + + const focusCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_CLIENT.FOCUS); + + focusCb[0][1](state); + + const blurCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_CLIENT.BLUR); + + + blurCb[0][1](state); + + blurCb[0][1]({ + ...state, + isValid: false, + isEmpty: false, + }); + + const changeCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_CLIENT.CHANGE); + + changeCb[0][1](state); + + const setCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.SET_VALUE); + setCb[0][1]({ + options: { + label: 'updatedLabel', + inputStyles, + labelStyles, + errorTextStyles, + table:'updatedTable', + column:'updateColumn', + placeholder:'XX', + validations:[ + { + type:ValidationRuleType.LENGTH_MATCH_RULE, + params:{ + min:2, + } + } + ], + skyflowID: 'updated-skyflow-id', + }, + }); + + element.setupInputField(); + }); + + test('FrameElement constructor : dropdown', () => { + const div = document.createElement('div'); + + const formElement = new IFrameFormElement(collect_element_dropdown, {}, context); + const element = new FrameElement(formElement, { + label: 'label', + inputStyles, + labelStyles, + errorTextStyles, + options:["dummyOptions"] + }, div); + + const inst = EventEmitter.mock.instances[0]; + const onSpy = inst.on.mock.calls; + + const focusCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_CLIENT.FOCUS); + + focusCb[0][1](state); + + const blurCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_CLIENT.BLUR); + + + blurCb[0][1](state); + + blurCb[0][1]({ + ...state, + isValid: false, + isEmpty: false, + }); + + const changeCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_CLIENT.CHANGE); + + changeCb[0][1](state); + + const setCb = onSpy + .filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.SET_VALUE); + setCb[0][1]({ + options: { + label: 'updatedLabel', + inputStyles, + labelStyles, + errorTextStyles, + table:'updatedTable', + column:'updateColumn', + placeholder:'XX', + validations:[ + { + type:ValidationRuleType.LENGTH_MATCH_RULE, + params:{ + min:2, + } + } + ], + skyflowID: 'updated-skyflow-id', + }, + }); + + element.setupInputField(); + }); + test('card element FrameElement with card type', () => { const cardElement = `element:CARD_NUMBER:${tableCol}`; diff --git a/tests/core/internal/frame-elements.test.js b/tests/core/internal/frame-elements.test.js index 60e31515..ceb8c256 100644 --- a/tests/core/internal/frame-elements.test.js +++ b/tests/core/internal/frame-elements.test.js @@ -79,15 +79,56 @@ describe('test frame elements', () => { windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:CARD_NUMBER:${btoa('123')}:ERROR`, location: { - href: `http://localhost/?${btoa(JSON.stringify(element))}`, + href: `http://localhost/?${btoa(JSON.stringify({record:element}))}`, } })); emitSpy = jest.spyOn(bus, 'emit'); }) + + test('FrameElements constructor : empty path', () => { + windowSpy.mockImplementation(() => ({ + name: `${FRAME_ELEMENT}:CARD_NUMBER:${btoa('123')}:ERROR`, + location: { + href: `http://localhost`, + } + })) + const onSpy = jest.spyOn(bus, 'on'); + + FrameElements.start() + + const emitEventName = emitSpy.mock.calls[0][0]; + const emitCb = emitSpy.mock.calls[0][2]; + expect(emitEventName.includes(ELEMENT_EVENTS_TO_IFRAME.FRAME_READY)).toBeTruthy() + emitCb(element); + + const mockCreateElement = jest.fn().mockImplementation(()=>{ + return { + resetEvents: jest.fn(), + on: jest.fn(), + getStatus: jest.fn(()=>({ + isFocused: false, + isValid: false, + isEmpty: true, + isComplete: false, + })), + fieldType: 'CARD_NUMBER', + state:{name:''}, + setValidation:jest.fn(), + setReplacePattern: jest.fn(), + setMask:jest.fn(), + getValue: jest.fn(), + setValue: jest.fn(), + } + }) + const frameElement = new FrameElements(mockCreateElement, {}, 'ERROR') + }) + test('FrameElements constructor', () => { const onSpy = jest.spyOn(bus, 'on'); + FrameElements.init(jest.fn().mockReturnValue({resetEvents:jest.fn(),on:jest.fn(),fieldType:'group',getStatus:jest.fn().mockReturnValue({}),state:{}}),{}) + FrameElements.start() const emitEventName = emitSpy.mock.calls[0][0]; @@ -127,7 +168,7 @@ describe('test composable frame elements', () => { windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:group:${btoa('123')}:ERROR`, location: { - href: `http://localhost/?${btoa(JSON.stringify(element))}`, + href: `http://localhost/?${btoa(JSON.stringify({record:element}))}`, } })); diff --git a/tests/core/internal/reveal/reveal-frame.test.js b/tests/core/internal/reveal/reveal-frame.test.js index 203c1527..4545b90e 100644 --- a/tests/core/internal/reveal/reveal-frame.test.js +++ b/tests/core/internal/reveal/reveal-frame.test.js @@ -67,6 +67,34 @@ describe("Reveal Frame Class",()=>{ }); }); + + test("init callback before reveal : without path",()=>{ + const data = { + record:{ + token:"1815-6223-1073-1425", + label:"Card Number", + altText:"xxxx-xxxx-xxxx-xxxx", + inputStyles:{ + base:{ + color:"red" + } + }, + labelStyles:{ + base:{ + color:"black" + } + } + }, + context: { logLevel: LogLevel.ERROR,env:Env.PROD} + } + defineUrl('http://localhost'); + try{ + RevealFrame.init() + }catch(e){ + expect(e).toBeDefined() + } + }); + test("init callback before reveal",()=>{ const data = { record:{ From dabcb0b94d6d49135cde7a16dc89bc2c11c860c4 Mon Sep 17 00:00:00 2001 From: Shashank Prajapati Date: Tue, 23 Jan 2024 14:57:33 +0530 Subject: [PATCH 32/32] SK-1395 : coverage update --- .../internal/iframe-form/iframe-form.test.js | 279 +++++++++++++++++- 1 file changed, 278 insertions(+), 1 deletion(-) diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index 6da537ad..73d72f42 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -14,6 +14,7 @@ import { parameterizedString } from '../../../../src/utils/logs-helper'; const tableCol = btoa('1234') const collect_element = `element:CVV:${tableCol}`; +const checkbox_element = `element:${ELEMENTS.checkbox.name}:${tableCol}` const file_element = `element:FILE_INPUT:${tableCol}`; const context = { @@ -572,6 +573,95 @@ describe('test iframeForm collect method', () => { }); }); + test('initialize iframeform and submit collect with invalid input : client has error', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + form.setClient(clientObj) + form.setClientMetadata(metaData) + form.setContext(context) + + const frameReadyEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + 'controllerId'); + const frameReadyCb = frameReadyEvent[0][1]; + expect(() => { frameReadyCb({}) }).toThrow(SkyflowError) + + frameReadyCb({ name: COLLECT_FRAME_CONTROLLER }) + + expect(() => { frameReadyCb({ name: "element:type:aW52YWxpZA==" }) }).toThrow(SkyflowError) + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + + frameReadyCb({ name:collect_element}) + + const createFormElement = skyflowInit.mock.calls[0][0] + const element = createFormElement(collect_element,undefined,undefined,true) + + const setErrorEvent = on.mock.calls.filter((data)=>data[0]===ELEMENT_EVENTS_TO_IFRAME.COLLECT_ELEMENT_SET_ERROR) + console.log("setErrorEvent",setErrorEvent) + const setErrorCb = setErrorEvent[0][1] + + setErrorCb({isTriggerError:true,clientErrorText:"Error",name:collect_element}) + + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); + const tokenizationCb = tokenizationEvent[0][1]; + const cb2 = jest.fn(); + + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2) + + setTimeout(() => { + expect(cb2.mock.calls[0][0].error.message).toBeDefined() + done() + }, 1000) + + + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + }) + test('initialize iframeform and submit collect with invalid input', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); form.setClient(clientObj) @@ -1145,6 +1235,65 @@ describe('test iframeForm collect method', () => { } })); }) + test('success with skyflowID', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(collect_element,"",{},context) + + element.setValue("123") + element.skyflowID = "testID" + element.tableName = "table" + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + + form.setClient(clientObj1) + form.setClientMetadata(metaData) + form.setContext(context) + + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); + const tokenizationCb = tokenizationEvent[0][1]; + const cb2 = jest.fn(); + tokenizationCb({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}, cb2); + setTimeout(() => { + expect(cb2.mock.calls[0][0].error).toBeDefined(); + done() + }, 1000) + form.tokenize({...data,elementIds:[{elementId:collect_element,frameId:collect_element}]}).then().catch( err => { + expect(err).toBeDefined(); + }) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + }) + test('success', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); const element = new IFrameFormElement(collect_element,"",{},context) @@ -1201,7 +1350,66 @@ describe('test iframeForm collect method', () => { } })); }) - test('insert records duplicate error', (done) => { + + + test('success : checkbox', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + const element = new IFrameFormElement(checkbox_element,"",{},context) + element.tableName = "tableName" + element.setValue(true) + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${checkbox_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + + form.setClient(clientObj1) + form.setClientMetadata(metaData) + form.setContext(context) + + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); + const tokenizationCb = tokenizationEvent[0][1]; + const cb2 = jest.fn(); + tokenizationCb({...data,elementIds:[{elementId:checkbox_element,frameId:checkbox_element}]}, cb2); + setTimeout(() => { + expect(cb2.mock.calls[0][0].error).toBeDefined(); + done() + }, 1000) + form.tokenize({...data,elementIds:[{elementId:checkbox_element,frameId:checkbox_element},{elementId:checkbox_element,frameId:checkbox_element}]}).then().catch( err => { + expect(err).toBeDefined(); + }) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + }) + + test('skyflowID error', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); form.setClient(clientObj) form.setClientMetadata(metaData) @@ -1210,6 +1418,9 @@ describe('test iframeForm collect method', () => { const element = new IFrameFormElement(collect_element,"",{},context) element.setValue("123") + element.tableName = "table" + element.state.name = collect_element + element.skyflowID = '' const skyflowInit = jest.fn(); let windowSpy = jest.spyOn(global, 'window', 'get'); windowSpy.mockImplementation(() => ({ @@ -1264,6 +1475,72 @@ describe('test iframeForm collect method', () => { } })); }) + + test('insert records duplicate error', (done) => { + const form = new IFrameForm("controllerId", "", "ERROR"); + form.setClient(clientObj) + form.setClientMetadata(metaData) + form.setContext(context) + + const element = new IFrameFormElement(collect_element,"",{},context) + + element.setValue("123") + element.tableName = "table" + element.state.name = collect_element + const skyflowInit = jest.fn(); + let windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + parent: { + frames: { + [`${collect_element}:controllerId:ERROR`]:{ + document:{ + getElementById:()=>({ + iFrameFormElement:element + }) + } + } + } + }, + location: { + href: 'http://iframe.html' + } + })); + const tokenizationEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST + 'controllerId'); + const tokenizationCb = tokenizationEvent[0][1]; + const cb2 = jest.fn(); + tokenizationCb({ + additionalFields: { + records: [{ + table: 'table', + fields: { + col: '123', + col: '123', + } + }] + }, + elementIds:[{elementId:collect_element,frameId:collect_element},{elementId:collect_element,frameId:collect_element}] + }, cb2) + setTimeout(() => { + expect(cb2.mock.calls[0][0].error.message).toBeDefined() + done() + }, 1000) + windowSpy.mockImplementation(() => ({ + parent: { + frames: [{ + name: collect_element, + location: { + href: 'http://iframe.html' + }, + Skyflow: { + init: skyflowInit + } + }] + }, + location: { + href: 'http://iframe.html' + } + })); + }) test('insert records with tokens as false', (done) => { const form = new IFrameForm("controllerId", "", "ERROR"); form.setClient(clientObj)