Skip to content

Commit

Permalink
Merge pull request #65 from revolut-engineering/version-script
Browse files Browse the repository at this point in the history
Fetch version script before loading embed
  • Loading branch information
atsikov authored Jun 27, 2023
2 parents 4bd483d + 69d73da commit b20d180
Show file tree
Hide file tree
Showing 12 changed files with 597 additions and 172 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ static/
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules

# IDE
.vscode
.history
35 changes: 23 additions & 12 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,30 @@ export const MODE = {

export type MODE = typeof MODE

export const URLS = {
[MODE.SANDBOX]: 'https://sandbox-merchant.revolut.com/embed.js',
[MODE.PRODUCTION]: 'https://merchant.revolut.com/embed.js',
[MODE.DEVELOPMENT]: 'https://merchant.revolut.codes/embed.js',
} as const
export type URLS = typeof URLS
type UrlsMap = Readonly<
Record<
MODE[keyof typeof MODE],
Readonly<{ embed: string; version: string; upsell: string }>
>
>

export const UPSELL_URLS = {
[MODE.SANDBOX]: 'https://sandbox-merchant.revolut.com/upsell/embed.js',
[MODE.PRODUCTION]: 'https://merchant.revolut.com/upsell/embed.js',
[MODE.DEVELOPMENT]: 'https://merchant.revolut.codes/upsell/embed.js',
} as const
export type UPSELL_URLS = typeof UPSELL_URLS
export const URLS: UrlsMap = {
[MODE.SANDBOX]: {
embed: 'https://sandbox-merchant.revolut.com/embed.js',
version: 'https://sandbox-merchant.revolut.com/version.js',
upsell: 'https://sandbox-merchant.revolut.com/upsell/embed.js',
},
[MODE.PRODUCTION]: {
embed: 'https://merchant.revolut.com/embed.js',
version: 'https://merchant.revolut.com/version.js',
upsell: 'https://merchant.revolut.com/upsell/embed.js',
},
[MODE.DEVELOPMENT]: {
embed: 'https://merchant.revolut.codes/embed.js',
version: 'https://merchant.revolut.codes/version.js',
upsell: 'https://merchant.revolut.codes/upsell/embed.js',
},
}

export const REVOLUT_PAY_ORDER_ID_URL_PARAMETER = '_rp_oid'
export const REVOLUT_PAY_SUCCESS_REDIRECT_URL_PARAMETER = '_rp_s'
Expand Down
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { RevolutCheckout } from './types'
declare global {
var RevolutCheckout: RevolutCheckout | undefined
var RevolutUpsell: RevolutCheckout['upsell'] | undefined
var __REV_PAY_VERSION__: string | undefined
}
209 changes: 155 additions & 54 deletions src/index.test.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,93 @@
import '@testing-library/jest-dom'
import { fireEvent } from '@testing-library/dom'
import {
triggerScriptOnLoad,
triggerScriptOnError,
settleVersionScript,
} from './testing'

afterEach(() => jest.resetModules())
afterEach(() => {
jest.resetModules()

document.querySelectorAll('script').forEach((el) => el.remove())
})

function setup() {
const RevolutCheckout = require('./index').default
const script = document.createElement('script')

const MockInstance = jest.fn()
const MockRevolutCheckout = jest.fn(() => MockInstance)

const TriggerSuccess = jest.fn(() => {
setTimeout(() => {
window.RevolutCheckout = MockRevolutCheckout
fireEvent.load(script)
})
})
const TriggerSuccessEmbed = jest.fn(
() =>
new Promise((resolve) => {
setTimeout(() => {
window.RevolutCheckout = MockRevolutCheckout
triggerScriptOnLoad('embed.js')
resolve()
})
})
)

const TriggerError = jest.fn(() => {
setTimeout(() => {
fireEvent.error(script)
})
})
const TriggerErrorEmbed = jest.fn(
() =>
new Promise((resolve) => {
setTimeout(() => {
triggerScriptOnError('embed.js')
resolve()
})
})
)

const TriggerSuccessVersion = (version) =>
settleVersionScript(() => {
window.__REV_PAY_VERSION__ = version
triggerScriptOnLoad('version.js')
}, version)

jest.spyOn(document, 'createElement').mockImplementationOnce(() => script)
const TriggerErrorVersion = () =>
settleVersionScript(() => {
triggerScriptOnError('version.js')
})

return {
script,
RevolutCheckout,
MockInstance,
MockRevolutCheckout,
TriggerSuccess,
TriggerError,
TriggerSuccessVersion,
TriggerErrorVersion,
TriggerSuccessEmbed,
TriggerErrorEmbed,
}
}

test(`should load embed script for 'dev'`, async () => {
const {
script,
RevolutCheckout,
MockInstance,
MockRevolutCheckout,
TriggerSuccess,
TriggerSuccessEmbed,
TriggerSuccessVersion,
} = setup()

const promise = RevolutCheckout('DEV_XXX', 'dev')
const spyLoad = jest.spyOn(script, 'onload')

expect(script).toHaveAttribute('id', 'revolut-checkout')
expect(script).toHaveAttribute(
const versionScript = document.querySelector('script#revolut-pay-version')
expect(versionScript).toHaveAttribute(
'src',
'https://merchant.revolut.codes/version.js'
)

await TriggerSuccessVersion('abc12345')

const embedScript = document.querySelector('script#revolut-checkout')
expect(embedScript).toHaveAttribute(
'src',
'https://merchant.revolut.codes/embed.js'
'https://merchant.revolut.codes/embed.js?version=abc12345'
)
const spyLoad = jest.spyOn(embedScript, 'onload')

TriggerSuccess()
await TriggerSuccessEmbed()

const instance = await promise

Expand All @@ -64,20 +98,32 @@ test(`should load embed script for 'dev'`, async () => {

test(`should load embed script for 'prod'`, async () => {
const {
script,
RevolutCheckout,
MockInstance,
MockRevolutCheckout,
TriggerSuccess,
TriggerSuccessVersion,
TriggerSuccessEmbed,
} = setup()

const promise = RevolutCheckout('PROD_XXX', 'prod')
const spyLoad = jest.spyOn(script, 'onload')

expect(script).toHaveAttribute('id', 'revolut-checkout')
expect(script).toHaveAttribute('src', 'https://merchant.revolut.com/embed.js')
const versionScript = document.querySelector('script#revolut-pay-version')
expect(versionScript).toHaveAttribute(
'src',
'https://merchant.revolut.com/version.js'
)

await TriggerSuccessVersion('abc12345')

const embedScript = document.querySelector('script#revolut-checkout')
expect(embedScript).toHaveAttribute(
'src',
'https://merchant.revolut.com/embed.js?version=abc12345'
)

const spyLoad = jest.spyOn(embedScript, 'onload')

TriggerSuccess()
await TriggerSuccessEmbed()

const instance = await promise

Expand All @@ -88,23 +134,32 @@ test(`should load embed script for 'prod'`, async () => {

test(`should load embed script for 'sandbox'`, async () => {
const {
script,
RevolutCheckout,
MockInstance,
MockRevolutCheckout,
TriggerSuccess,
TriggerSuccessVersion,
TriggerSuccessEmbed,
} = setup()

const promise = RevolutCheckout('SANDBOX_XXX', 'sandbox')
const spyLoad = jest.spyOn(script, 'onload')

expect(script).toHaveAttribute('id', 'revolut-checkout')
expect(script).toHaveAttribute(
const versionScript = document.querySelector('script#revolut-pay-version')
expect(versionScript).toHaveAttribute(
'src',
'https://sandbox-merchant.revolut.com/embed.js'
'https://sandbox-merchant.revolut.com/version.js'
)

TriggerSuccess()
await TriggerSuccessVersion('abc12345')

const embedScript = document.querySelector('script#revolut-checkout')
expect(embedScript).toHaveAttribute(
'src',
'https://sandbox-merchant.revolut.com/embed.js?version=abc12345'
)

const spyLoad = jest.spyOn(embedScript, 'onload')

await TriggerSuccessEmbed()

const instance = await promise

Expand All @@ -114,11 +169,17 @@ test(`should load embed script for 'sandbox'`, async () => {
})

test('should not request new embed script and use loaded one', async () => {
const { RevolutCheckout, MockRevolutCheckout, TriggerSuccess } = setup()
const {
RevolutCheckout,
MockRevolutCheckout,
TriggerSuccessVersion,
TriggerSuccessEmbed,
} = setup()

const promise = RevolutCheckout('XXX_1')

TriggerSuccess()
await TriggerSuccessVersion('')
await TriggerSuccessEmbed()

await promise
expect(MockRevolutCheckout).toHaveBeenCalledWith('XXX_1')
Expand All @@ -129,20 +190,26 @@ test('should not request new embed script and use loaded one', async () => {

test(`should use 'prod' by default`, async () => {
const {
script,
RevolutCheckout,
MockInstance,
MockRevolutCheckout,
TriggerSuccess,
TriggerSuccessVersion,
TriggerSuccessEmbed,
} = setup()

const promise = RevolutCheckout('PROD_XXX')
const spyLoad = jest.spyOn(script, 'onload')

expect(script).toHaveAttribute('id', 'revolut-checkout')
expect(script).toHaveAttribute('src', 'https://merchant.revolut.com/embed.js')
await TriggerSuccessVersion('')

TriggerSuccess()
const embedScript = document.querySelector('script#revolut-checkout')
expect(embedScript).toHaveAttribute(
'src',
'https://merchant.revolut.com/embed.js'
)

const spyLoad = jest.spyOn(embedScript, 'onload')

await TriggerSuccessEmbed()

const instance = await promise

Expand All @@ -151,15 +218,40 @@ test(`should use 'prod' by default`, async () => {
expect(MockRevolutCheckout).toHaveBeenCalledWith('PROD_XXX')
})

test('should throw error on failed loading', async () => {
test('should load embed script without version parameter if version script fails to load', async () => {
const {
RevolutCheckout,
MockRevolutCheckout,
TriggerErrorVersion,
TriggerSuccessEmbed,
} = setup()

const promise = RevolutCheckout('PROD_XXX')

await TriggerErrorVersion()

const embedScript = document.querySelector('script#revolut-checkout')
expect(embedScript).toHaveAttribute(
'src',
'https://merchant.revolut.com/embed.js'
)

await TriggerSuccessEmbed()

await promise
expect(MockRevolutCheckout).toHaveBeenCalledWith('PROD_XXX')
})

test('should throw error on failed embed script loading', async () => {
expect.assertions(1)

const { RevolutCheckout, TriggerError } = setup()
const { RevolutCheckout, TriggerSuccessVersion, TriggerErrorEmbed } = setup()

try {
const promise = RevolutCheckout('PROD_XXX')

TriggerError()
await TriggerSuccessVersion()
await TriggerErrorEmbed()

await promise
} catch (error) {
Expand All @@ -170,18 +262,27 @@ test('should throw error on failed loading', async () => {
})

test('should throw error if RevolutCheckout is missing', async () => {
const { script, RevolutCheckout, TriggerSuccess } = setup()
const {
RevolutCheckout,
TriggerSuccessVersion,
TriggerSuccessEmbed,
} = setup()

const promise = RevolutCheckout('PROD_XXX')

expect(script).toHaveAttribute('id', 'revolut-checkout')
expect(script).toHaveAttribute('src', 'https://merchant.revolut.com/embed.js')
await TriggerSuccessVersion()

const embedScript = document.querySelector('script#revolut-checkout')
expect(embedScript).toHaveAttribute(
'src',
'https://merchant.revolut.com/embed.js'
)

TriggerSuccess.mockImplementationOnce(() => {
TriggerSuccessEmbed.mockImplementationOnce(() => {
// RevolutCheckout is not assigned to window
fireEvent.load(script)
fireEvent.load(embedScript)
})
TriggerSuccess()
await TriggerSuccessEmbed()

await expect(promise).rejects.toEqual(
new Error(
Expand Down
Loading

0 comments on commit b20d180

Please sign in to comment.