Skip to content

Commit b3adb8e

Browse files
committed
fix: treat an explicit undefined parameters argument as {}
withParameterDefaults detected "no parameters" for any non-object in the slot, then inserted the filled object after it. So calling a method with an explicit undefined — a type-valid call, since the parameter is optional — e.g. payments.page(undefined) or payments.get(id, undefined), left the undefined in the parameters position and silently dropped the defaults. Now the slot is replaced when it is occupied (an object, or an explicit undefined/null standing in for {}); insertion only happens when no parameters argument was passed (a leading id, or nothing). This also flattens the branch. Adds tests covering the undefined case on both id-less and id-methods.
1 parent cc9f627 commit b3adb8e

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

src/plumbing/withParameterDefaults.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,23 @@ export default function withParameterDefaults<T extends object>(target: T, netwo
6363
// (no `enumerable` ‒ the wrappers stay non-enumerable, like the prototype methods they shadow)
6464
value(this: unknown, ...args: unknown[]) {
6565
// The parameters object sits just before a trailing callback (or is the last argument). The `id` is a
66-
// string and the callback is a function, so an object at that position is always the parameters.
66+
// string and the callback is a function, so a non-string at that position is the parameters ‒ which the
67+
// caller may have passed explicitly as `undefined`, equivalent to `{}`.
6768
const slot = typeof args[args.length - 1] == 'function' ? args.length - 1 : args.length;
6869
const parameters = args[slot - 1];
6970
const hasParameters = parameters != null && typeof parameters == 'object';
7071
const base = (hasParameters ? parameters : {}) as Record<string, unknown>;
7172
const filled = applyDefaults(networkClient.parameterDefaults, base, keys as Array<keyof ParameterDefaults>);
72-
if (hasParameters) {
73+
// Nothing was filled (no defaults apply, or the call already set them) ‒ leave the arguments untouched.
74+
if (filled === base) {
75+
return original.apply(this, args);
76+
}
77+
// Replace the parameters slot when it is occupied ‒ by an object, or by an explicit `undefined`/`null`
78+
// standing in for `{}`. Otherwise only a leading `id` (a string) or nothing was passed, so insert the
79+
// filled object before any trailing callback.
80+
if (slot >= 1 && typeof parameters != 'string') {
7381
args[slot - 1] = filled;
74-
} else if (filled !== base) {
75-
// No parameters object was passed, but a default applied ‒ insert it (before any trailing callback).
82+
} else {
7683
args.splice(slot, 0, filled);
7784
}
7885
return original.apply(this, args);

tests/unit/defaults.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,28 @@ test('injects the client defaults even when no parameters object is passed', ()
5151
});
5252
});
5353

54+
test('treats an explicit undefined parameters argument the same as {}', () => {
55+
return new NetworkMocker(clientWith({ testmode: true, profileId: 'pfl_mock' })).use(async ([mollieClient, networkMocker]) => {
56+
networkMocker.intercept('GET', '/payments?testmode=true&profileId=pfl_mock', 200, paymentsPage).twice();
57+
58+
// Passing `undefined` is type-valid (the parameter is optional) and must inject just like `{}` ‒ not be dropped.
59+
const payments = await bluster(mollieClient.payments.page.bind(mollieClient.payments))(undefined);
60+
61+
expect(payments.length).toBe(1);
62+
});
63+
});
64+
65+
test('treats an explicit undefined parameters argument the same as {} for id-methods', () => {
66+
return new NetworkMocker(clientWith({ testmode: true, profileId: 'pfl_mock' })).use(async ([mollieClient, networkMocker]) => {
67+
// payments.get accepts testmode (not profileId), so only testmode is injected into the query.
68+
networkMocker.intercept('GET', '/payments/tr_mock0001?testmode=true', 200, payment).twice();
69+
70+
const result = await bluster(mollieClient.payments.get.bind(mollieClient.payments))('tr_mock0001', undefined);
71+
72+
expect(result.id).toBe('tr_mock0001');
73+
});
74+
});
75+
5476
test('injects the client defaults into the request body', () => {
5577
return new NetworkMocker(clientWith({ testmode: true, profileId: 'pfl_mock' })).use(async ([mollieClient, networkMocker]) => {
5678
let capturedBody: any;

0 commit comments

Comments
 (0)