Skip to content

Commit

Permalink
test: add some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
longyulongyu committed Dec 5, 2023
1 parent 8f80c3f commit 77c88ef
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 56 deletions.
2 changes: 1 addition & 1 deletion packages/lib/src/components/PayMe/PayMe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class PayMeElement extends QRLoaderContainer {
redirectIntroduction: 'payme.openPayMeApp',
introduction: 'payme.scanQrCode',
timeToPay: 'payme.timeToPay',
redirectButtonLabel: 'payme.redirectButtonLabel',
buttonLabel: 'payme.redirectButtonLabel',
instructions: Instructions,
...super.formatProps(props)
};
Expand Down
1 change: 0 additions & 1 deletion packages/lib/src/components/helpers/QRLoaderContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export interface QRLoaderContainerProps extends UIElementProps {
paymentData?: string;
introduction: string;
redirectIntroduction?: string;
redirectButtonLabel?: string;
timeToPay?: string;
instructions?: string | (() => h.JSX.Element);
copyBtn?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class CountdownA11yReporter {

constructor(props: ICountdownA11yService) {
const { srPanel, i18n } = props;

this.srPanel = srPanel;
this.i18n = i18n;
// Force the srPanel to update ariaRelevant
Expand Down
154 changes: 108 additions & 46 deletions packages/lib/src/components/internal/QRLoader/QRLoader.test.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
import { mount } from 'enzyme';
import { render, screen, waitForElementToBeRemoved } from '@testing-library/preact';
import { h } from 'preact';
import QRLoader from './QRLoader';
import checkPaymentStatus from '../../../core/Services/payment-status';
import Language from '../../../language/Language';
import SRPanelProvider from '../../../core/Errors/SRPanelProvider';
import { SRPanel } from '../../../core/Errors/SRPanel';
import AdyenCheckoutError from '../../../core/Errors/AdyenCheckoutError';

jest.mock('../../../core/Services/payment-status');

const i18n = { get: key => key } as Language;

describe('WeChat', () => {
describe('QRLoader', () => {
const assignSpy = jest.fn();
const srPanel = new SRPanel({});
const customRender = (ui: h.JSX.Element) => {
return render(
// @ts-ignore ignore
<SRPanelProvider srPanel={srPanel}>{ui}</SRPanelProvider>
);
};

beforeAll(() => {
Object.defineProperty(window, 'matchMedia', {
value: jest.fn(() => ({ matches: true }))
});
Object.defineProperty(window, 'location', {
value: { assign: assignSpy }
});
});

describe('checkStatus', () => {
describe('QR loader in different statuses', () => {
// Pending status
test('checkStatus processes a pending response', () => {
// Mock the checkPaymentStatusValue with a pending response
const checkPaymentStatusValue = { payload: 'Ab02b4c0!', resultCode: 'pending', type: 'complete' };
(checkPaymentStatus as jest.Mock).mockReturnValueOnce(Promise.resolve(checkPaymentStatusValue));

const qrloader = mount(<QRLoader i18n={i18n} />);
qrloader
.instance()
.checkStatus()
.then(status => {
expect(status.props).toEqual(checkPaymentStatusValue);
expect(status.type).toBe('pending');
});
test('should show a spinner on init', () => {
const props = { brandLogo: 'https://example.com', brandName: 'example' };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
expect(screen.getByTestId('spinner')).toBeInTheDocument();
});

// Authorised status
test('checkStatus processes a authorised response', () => {
test('should show the success result', async () => {
// Mock the checkPaymentStatusValue with an authorised response
const checkPaymentStatusValue = { payload: 'Ab02b4c0!', resultCode: 'authorised', type: 'complete' };
(checkPaymentStatus as jest.Mock).mockReturnValueOnce(Promise.resolve(checkPaymentStatusValue));

const onCompleteMock = jest.fn();
const onErrorMock = jest.fn();
const expectedResult = {
data: {
details: {
Expand All @@ -49,40 +54,97 @@ describe('WeChat', () => {
}
};

const qrloader = mount(<QRLoader i18n={i18n} paymentData={'Ab02b4c0!2'} onComplete={onCompleteMock} onError={onErrorMock} />);
qrloader
.instance()
.checkStatus()
.then(status => {
expect(status.props).toEqual(checkPaymentStatusValue);
expect(status.type).toBe('success');
expect(onCompleteMock.mock.calls.length).toBe(1);
expect(onCompleteMock).toBeCalledWith(expectedResult, expect.any(Object));
expect(onErrorMock.mock.calls.length).toBe(0);
expect(qrloader.state('completed')).toBe(true);
});
const props = { paymentData: 'Ab02b4c0!2', onComplete: onCompleteMock, delay: 0 };
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByText(/payment successful/i)).toBeInTheDocument();
expect(onCompleteMock).toBeCalledWith(expectedResult, expect.any(Object));
});

// Error status
test('checkStatus processes an error response', () => {
test('should show the error result', async () => {
// Mock the checkPaymentStatusValue with an error
const checkPaymentStatusValue = { error: 'Unkown error' };
(checkPaymentStatus as jest.Mock).mockReturnValueOnce(Promise.resolve(checkPaymentStatusValue));

const mockErrorStatusNoPayload = { error: 'Unkown error', payload: 'Ab02b4c0!' };
(checkPaymentStatus as jest.Mock).mockReturnValueOnce(Promise.resolve(mockErrorStatusNoPayload));
const onCompleteMock = jest.fn();
const onErrorMock = jest.fn();
const expectedResult = {
data: {
details: {
payload: 'Ab02b4c0!' // this should come from the status endpoint
},
paymentData: 'Ab02b4c0!2' // this should come from the initial props (from the initial /payments call)
}
};

const props = { paymentData: 'Ab02b4c0!2', onComplete: onCompleteMock, onError: onErrorMock, delay: 0 };
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByText(/payment failed/i)).toBeInTheDocument();
expect(onErrorMock).toBeCalledWith(new AdyenCheckoutError('ERROR', 'error result with no payload in response'));
expect(onCompleteMock).toBeCalledWith(expectedResult, expect.any(Object));
});
});

describe('QR code is not expired', () => {
beforeEach(() => {
const mockPaymentStatus = { payload: 'Ab02b4c0!', resultCode: 'pending', type: 'complete' };
(checkPaymentStatus as jest.Mock).mockResolvedValue(mockPaymentStatus);
});

test('should see the brand logo img', async () => {
const props = { brandLogo: 'https://example.com', brandName: 'example', delay: 0 };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
await waitForElementToBeRemoved(() => screen.queryByTestId('spinner'));
expect(await screen.findByAltText(props.brandName)).toBeInTheDocument();
});

test('should see the payment amount', async () => {
const props = { amount: { value: 1000, currency: 'NL' }, delay: 0 };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByText(props.amount.value)).toBeInTheDocument();
});

test('should see the copy button', async () => {
const props = { copyBtn: true, delay: 0 };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByRole('button', { name: /copy/i })).toBeInTheDocument();
});

describe('Redirect to mobile app section', () => {
test('should see the redirect introduction', async () => {
const props = { url: 'https://localhost:8080', delay: 0, redirectIntroduction: 'redirectIntroduction' };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByText(props.redirectIntroduction)).toBeInTheDocument();
});

test('should see the redirect button', async () => {
const props = { url: 'https://localhost:8080', delay: 0, buttonLabel: 'Redirect' };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
const btn = await screen.findByRole('button', { name: /redirect/i });
expect(btn).toBeInTheDocument();
btn.click();
expect(assignSpy).toHaveBeenCalledWith(props.url);
});
});

describe('QR code section', () => {
test('should see the QR code instructions string', async () => {
const props = { instructions: 'instructions', delay: 0 };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByText(props.instructions)).toBeInTheDocument();
});

const qrloader = mount(<QRLoader i18n={i18n} onComplete={onCompleteMock} onError={onErrorMock} />);

qrloader
.instance()
.checkStatus()
.then(status => {
expect(status.props).toEqual(checkPaymentStatusValue);
expect(status.type).toBe('error');
expect(onCompleteMock.mock.calls.length).toBe(0);
expect(onErrorMock.mock.calls.length).toBe(1);
});
test('should see the QR code instructions element', async () => {
const props = { instructions: () => <div data-testid="test-instructions">instructions</div>, delay: 0 };
// @ts-ignore ignore
customRender(<QRLoader i18n={i18n} {...props} />);
expect(await screen.findByTestId('test-instructions')).toBeInTheDocument();
});
});
});
});
9 changes: 2 additions & 7 deletions packages/lib/src/components/internal/QRLoader/QRLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
throttledInterval: 10000,
introduction: 'wechatpay.scanqrcode',
timeToPay: 'wechatpay.timetopay',
redirectButtonLabel: 'openApp'
buttonLabel: 'openApp'
};

// Retry until getting a complete response from the server or it times out\
Expand Down Expand Up @@ -146,7 +146,6 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
const { i18n, loadingContext } = useCoreContext();
const getImage = useImage();
const qrCodeImage = this.props.qrCodeData ? `${loadingContext}${QRCODE_URL}${this.props.qrCodeData}` : this.props.qrCodeImage;

const finalState = (image, message) => {
const status = i18n.get(message);
useA11yReporter(status);
Expand Down Expand Up @@ -202,11 +201,7 @@ class QRLoader extends Component<QRLoaderProps, QRLoaderState> {
{this.props.redirectIntroduction && (
<div className="adyen-checkout__qr-loader__subtitle">{i18n.get(this.props.redirectIntroduction)}</div>
)}
<Button
classNameModifiers={['qr-loader']}
onClick={() => this.redirectToApp(url)}
label={i18n.get(this.props.redirectButtonLabel)}
/>
<Button classNameModifiers={['qr-loader']} onClick={() => this.redirectToApp(url)} label={i18n.get(this.props.buttonLabel)} />
<ContentSeparator />
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/components/internal/QRLoader/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export interface QRLoaderProps {
classNameModifiers?: string[];
brandLogo?: string;
brandName?: string;
buttonLabel?: string;
introduction?: string;
redirectIntroduction?: string;
redirectButtonLabel?: string;
timeToPay?: string;
instructions?: string | (() => h.JSX.Element);
copyBtn?: boolean;
Expand Down

0 comments on commit 77c88ef

Please sign in to comment.