Skip to content

test: cover the public pay SDK contract#38

Open
Peter7896 wants to merge 2 commits into
dojo-engineering:mainfrom
Peter7896:peter7896/pay-sdk-tests
Open

test: cover the public pay SDK contract#38
Peter7896 wants to merge 2 commits into
dojo-engineering:mainfrom
Peter7896:peter7896/pay-sdk-tests

Conversation

@Peter7896

Copy link
Copy Markdown

Summary

  • replace the placeholder SDK test with public API coverage for the React Native wrapper
  • validate every exported ResultCode enum value and confirm the native bridge returns those values through both flows
  • verify both public entry points forward PaymentDetails and raise the documented linking error when the native module is unavailable

Closes #35

Testing

  • npm test -- --runInBand
  • npm run lint -- src/__tests__/index.test.tsx
  • npx tsc --noEmit -p tsconfig.build.json

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces a placeholder test with a comprehensive test suite for the React Native SDK contract, verifying result codes, payment/setup flow forwarding, and linking error handling. The feedback suggests parameterizing the test suite to cover both the Legacy Bridge and TurboModule (New Architecture) execution paths, rather than only testing the legacy path.

Comment thread src/__tests__/index.test.tsx Outdated
Comment on lines +31 to +48
function loadSdk(nativeModule?: NativeModule) {
jest.resetModules();
// @ts-expect-error test-only override
global.__turboModuleProxy = null;

jest.doMock('react-native', () => ({
NativeModules: nativeModule ? { DojoReactNativePaySdk: nativeModule } : {},
Platform: {
select: (options: Record<string, string>) =>
options.ios ?? options.default ?? '',
},
TurboModuleRegistry: {
getEnforcing: jest.fn(() => nativeModule),
},
}));

return require('../index') as typeof import('../index');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The loadSdk helper currently hardcodes global.__turboModuleProxy = null, which means the TurboModule (New Architecture) path is never tested. We can add an optional isTurboModuleEnabled parameter to allow testing both legacy and TurboModule paths.

Suggested change
function loadSdk(nativeModule?: NativeModule) {
jest.resetModules();
// @ts-expect-error test-only override
global.__turboModuleProxy = null;
jest.doMock('react-native', () => ({
NativeModules: nativeModule ? { DojoReactNativePaySdk: nativeModule } : {},
Platform: {
select: (options: Record<string, string>) =>
options.ios ?? options.default ?? '',
},
TurboModuleRegistry: {
getEnforcing: jest.fn(() => nativeModule),
},
}));
return require('../index') as typeof import('../index');
}
function loadSdk(nativeModule?: NativeModule, isTurboModuleEnabled = false) {
jest.resetModules();
// @ts-expect-error test-only override
global.__turboModuleProxy = isTurboModuleEnabled ? {} : null;
jest.doMock('react-native', () => ({
NativeModules: nativeModule ? { DojoReactNativePaySdk: nativeModule } : {},
Platform: {
select: (options: Record<string, string>) =>
options.ios ?? options.default ?? '',
},
TurboModuleRegistry: {
getEnforcing: jest.fn(() => nativeModule),
},
}));
return require('../index') as typeof import('../index');
}

Comment thread src/__tests__/index.test.tsx Outdated
Comment on lines +50 to +120
describe('public SDK contract', () => {
afterEach(() => {
jest.resetModules();
jest.clearAllMocks();
jest.dontMock('react-native');
// @ts-expect-error test-only cleanup
delete global.__turboModuleProxy;
});

it('exposes the full ResultCode enum without missing values', () => {
const { ResultCode } = loadSdk({
startPaymentFlow: jest.fn(),
startSetupFlow: jest.fn(),
});

const actualEntries = Object.entries(ResultCode).filter(
(entry): entry is [string, number] => typeof entry[1] === 'number'
);

expect(Object.fromEntries(actualEntries)).toEqual(EXPECTED_RESULT_CODES);
expect(actualEntries).toHaveLength(14);
});

it('forwards payment details to startPaymentFlow and resolves with the native result code', async () => {
const nativeModule = {
startPaymentFlow: jest.fn().mockResolvedValue(3),
startSetupFlow: jest.fn(),
};
const { startPaymentFlow, ResultCode } = loadSdk(nativeModule);

await expect(startPaymentFlow(PAYMENT_DETAILS)).resolves.toBe(
ResultCode.authorizing
);
expect(nativeModule.startPaymentFlow).toHaveBeenCalledWith(PAYMENT_DETAILS);
});

it('forwards payment details to startSetupFlow and resolves with the native result code', async () => {
const nativeModule = {
startPaymentFlow: jest.fn(),
startSetupFlow: jest.fn().mockResolvedValue(0),
};
const { startSetupFlow, ResultCode } = loadSdk(nativeModule);

await expect(startSetupFlow(PAYMENT_DETAILS)).resolves.toBe(
ResultCode.successful
);
expect(nativeModule.startSetupFlow).toHaveBeenCalledWith(PAYMENT_DETAILS);
});

it('throws the linking error when startPaymentFlow is unavailable', () => {
const { startPaymentFlow } = loadSdk();

expect(() => startPaymentFlow(PAYMENT_DETAILS)).toThrow(
"@dojo-engineering/react-native-pay-sdk' doesn't seem to be linked"
);
expect(() => startPaymentFlow(PAYMENT_DETAILS)).toThrow(
"You have run 'pod install'"
);
});

it('throws the linking error when startSetupFlow is unavailable', () => {
const { startSetupFlow } = loadSdk();

expect(() => startSetupFlow(PAYMENT_DETAILS)).toThrow(
"@dojo-engineering/react-native-pay-sdk' doesn't seem to be linked"
);
expect(() => startSetupFlow(PAYMENT_DETAILS)).toThrow(
'You rebuilt the app after installing the package'
);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure full coverage of both the Legacy Bridge and TurboModule architectures, we can parameterize the entire test suite using describe.each and pass the isTurboModuleEnabled flag to loadSdk.

describe.each([
  { name: 'Legacy Bridge', isTurboModuleEnabled: false },
  { name: 'TurboModule', isTurboModuleEnabled: true },
])('public SDK contract ($name)', ({ isTurboModuleEnabled }) => {
  afterEach(() => {
    jest.resetModules();
    jest.clearAllMocks();
    jest.dontMock('react-native');
    // @ts-expect-error test-only cleanup
    delete global.__turboModuleProxy;
  });

  it('exposes the full ResultCode enum without missing values', () => {
    const { ResultCode } = loadSdk({
      startPaymentFlow: jest.fn(),
      startSetupFlow: jest.fn(),
    }, isTurboModuleEnabled);

    const actualEntries = Object.entries(ResultCode).filter(
      (entry): entry is [string, number] => typeof entry[1] === 'number'
    );

    expect(Object.fromEntries(actualEntries)).toEqual(EXPECTED_RESULT_CODES);
    expect(actualEntries).toHaveLength(14);
  });

  it('forwards payment details to startPaymentFlow and resolves with the native result code', async () => {
    const nativeModule = {
      startPaymentFlow: jest.fn().mockResolvedValue(3),
      startSetupFlow: jest.fn(),
    };
    const { startPaymentFlow, ResultCode } = loadSdk(nativeModule, isTurboModuleEnabled);

    await expect(startPaymentFlow(PAYMENT_DETAILS)).resolves.toBe(
      ResultCode.authorizing
    );
    expect(nativeModule.startPaymentFlow).toHaveBeenCalledWith(PAYMENT_DETAILS);
  });

  it('forwards payment details to startSetupFlow and resolves with the native result code', async () => {
    const nativeModule = {
      startPaymentFlow: jest.fn(),
      startSetupFlow: jest.fn().mockResolvedValue(0),
    };
    const { startSetupFlow, ResultCode } = loadSdk(nativeModule, isTurboModuleEnabled);

    await expect(startSetupFlow(PAYMENT_DETAILS)).resolves.toBe(
      ResultCode.successful
    );
    expect(nativeModule.startSetupFlow).toHaveBeenCalledWith(PAYMENT_DETAILS);
  });

  it('throws the linking error when startPaymentFlow is unavailable', () => {
    const { startPaymentFlow } = loadSdk(undefined, isTurboModuleEnabled);

    expect(() => startPaymentFlow(PAYMENT_DETAILS)).toThrow(
      "@dojo-engineering/react-native-pay-sdk' doesn't seem to be linked"
    );
    expect(() => startPaymentFlow(PAYMENT_DETAILS)).toThrow(
      "You have run 'pod install'"
    );
  });

  it('throws the linking error when startSetupFlow is unavailable', () => {
    const { startSetupFlow } = loadSdk(undefined, isTurboModuleEnabled);

    expect(() => startSetupFlow(PAYMENT_DETAILS)).toThrow(
      "@dojo-engineering/react-native-pay-sdk' doesn't seem to be linked"
    );
    expect(() => startSetupFlow(PAYMENT_DETAILS)).toThrow(
      'You rebuilt the app after installing the package'
    );
  });
})

@Peter7896

Copy link
Copy Markdown
Author

Addressed the Gemini suggestions in 18cdbce by covering both Legacy Bridge and TurboModule paths in the public SDK contract tests. Validated locally with npm test -- --runInBand, npm run lint -- src/__tests__/index.test.tsx, and npx tsc --noEmit -p tsconfig.build.json.

@Peter7896
Peter7896 marked this pull request as ready for review June 24, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SDK has no real tests — src/__tests__/index.test.tsx only contains it.todo('write a test')

1 participant