@contractkit/plugin-typescript 0.17.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$ci.log +7 -7
- package/.turbo/turbo-test$colon$ci.log +15 -15
- package/CHANGELOG.md +21 -0
- package/README.md +10 -1
- package/coverage/clover.xml +795 -573
- package/coverage/coverage-final.json +6 -6
- package/coverage/index.html +19 -19
- package/coverage/src/codegen-contract.ts.html +3 -3
- package/coverage/src/codegen-operation.ts.html +1 -1
- package/coverage/src/codegen-plain-types.ts.html +1 -1
- package/coverage/src/codegen-sdk.ts.html +989 -236
- package/coverage/src/index.html +39 -39
- package/coverage/src/index.ts.html +1386 -306
- package/coverage/src/path-utils.ts.html +51 -51
- package/coverage/src/ts-render.ts.html +4 -4
- package/coverage/tests/helpers.ts.html +15 -15
- package/coverage/tests/index.html +1 -1
- package/dist/codegen-sdk.d.ts +96 -2
- package/dist/codegen-sdk.d.ts.map +1 -1
- package/dist/index.d.ts +8 -36
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +685 -137
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/codegen-sdk.ts +258 -7
- package/src/index.ts +556 -196
- package/tests/codegen-sdk.test.ts +188 -16
- package/tests/codegen-server.test.ts +101 -1
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
generateSdkAggregator,
|
|
6
6
|
deriveClientClassName,
|
|
7
7
|
deriveClientPropertyName,
|
|
8
|
+
deriveAreaClientClassName,
|
|
9
|
+
deriveAreaPropertyName,
|
|
10
|
+
deriveSubareaClientClassName,
|
|
11
|
+
deriveSubareaPropertyName,
|
|
12
|
+
getAreaSubarea,
|
|
8
13
|
hasPublicOperations,
|
|
9
14
|
} from '../src/codegen-sdk.js';
|
|
10
15
|
import { collectPublicTypeNames } from '@contractkit/core';
|
|
@@ -762,11 +767,14 @@ describe('generateSdkOptions', () => {
|
|
|
762
767
|
});
|
|
763
768
|
|
|
764
769
|
describe('generateSdkAggregator', () => {
|
|
765
|
-
it('generates Sdk class wrapping all clients', () => {
|
|
766
|
-
const out = generateSdkAggregator(
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
+
it('generates Sdk class wrapping all top-level clients', () => {
|
|
771
|
+
const out = generateSdkAggregator({
|
|
772
|
+
topLevelClients: [
|
|
773
|
+
{ className: 'UsersClient', propertyName: 'users', importPath: './src/users.client.js' },
|
|
774
|
+
{ className: 'CategoriesClient', propertyName: 'categories', importPath: './src/categories.client.js' },
|
|
775
|
+
],
|
|
776
|
+
areas: [],
|
|
777
|
+
});
|
|
770
778
|
expect(out).toContain("import type { SdkOptions } from './sdk-options.js'");
|
|
771
779
|
expect(out).toContain("import { createSdkFetch } from './sdk-options.js'");
|
|
772
780
|
expect(out).toContain("import { UsersClient } from './src/users.client.js'");
|
|
@@ -781,10 +789,11 @@ describe('generateSdkAggregator', () => {
|
|
|
781
789
|
});
|
|
782
790
|
|
|
783
791
|
it('uses custom sdkOptionsImportPath', () => {
|
|
784
|
-
const out = generateSdkAggregator(
|
|
785
|
-
[{ className: 'UsersClient', propertyName: 'users', importPath: './users.client.js' }],
|
|
786
|
-
|
|
787
|
-
|
|
792
|
+
const out = generateSdkAggregator({
|
|
793
|
+
topLevelClients: [{ className: 'UsersClient', propertyName: 'users', importPath: './users.client.js' }],
|
|
794
|
+
areas: [],
|
|
795
|
+
sdkOptionsImportPath: '../shared/sdk-options.js',
|
|
796
|
+
});
|
|
788
797
|
expect(out).toContain("from '../shared/sdk-options.js'");
|
|
789
798
|
});
|
|
790
799
|
});
|
|
@@ -1401,17 +1410,17 @@ describe('generateSdk — type import paths', () => {
|
|
|
1401
1410
|
|
|
1402
1411
|
describe('generateSdkAggregator — additional cases', () => {
|
|
1403
1412
|
it('uses custom sdkClassName', () => {
|
|
1404
|
-
const out = generateSdkAggregator(
|
|
1405
|
-
[{ className: 'UsersClient', propertyName: 'users', importPath: './users.client.js' }],
|
|
1406
|
-
|
|
1407
|
-
'ApiClient',
|
|
1408
|
-
);
|
|
1413
|
+
const out = generateSdkAggregator({
|
|
1414
|
+
topLevelClients: [{ className: 'UsersClient', propertyName: 'users', importPath: './users.client.js' }],
|
|
1415
|
+
areas: [],
|
|
1416
|
+
sdkClassName: 'ApiClient',
|
|
1417
|
+
});
|
|
1409
1418
|
expect(out).toContain('export class ApiClient {');
|
|
1410
1419
|
expect(out).not.toContain('export class Sdk {');
|
|
1411
1420
|
});
|
|
1412
1421
|
|
|
1413
|
-
it('handles empty
|
|
1414
|
-
const out = generateSdkAggregator([]);
|
|
1422
|
+
it('handles empty input', () => {
|
|
1423
|
+
const out = generateSdkAggregator({ topLevelClients: [], areas: [] });
|
|
1415
1424
|
expect(out).toContain('export class Sdk {');
|
|
1416
1425
|
expect(out).toContain('constructor(options: SdkOptions)');
|
|
1417
1426
|
expect(out).not.toContain('readonly ');
|
|
@@ -1498,3 +1507,166 @@ describe('generateSdk — json type in query / headers / params', () => {
|
|
|
1498
1507
|
expect(out).toContain('export type JsonValue =');
|
|
1499
1508
|
});
|
|
1500
1509
|
});
|
|
1510
|
+
|
|
1511
|
+
// ─── Area / subarea name derivation ───────────────────────────────────────
|
|
1512
|
+
|
|
1513
|
+
describe('area + subarea name derivation', () => {
|
|
1514
|
+
it('builds area client class and property names', () => {
|
|
1515
|
+
expect(deriveAreaClientClassName('identity')).toBe('IdentityClient');
|
|
1516
|
+
expect(deriveAreaPropertyName('identity')).toBe('identity');
|
|
1517
|
+
expect(deriveAreaClientClassName('payment-flows')).toBe('PaymentFlowsClient');
|
|
1518
|
+
expect(deriveAreaPropertyName('payment-flows')).toBe('paymentFlows');
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
it('builds subarea (concatenated) client class and property names', () => {
|
|
1522
|
+
expect(deriveSubareaClientClassName('identity', 'invitations')).toBe('IdentityInvitationsClient');
|
|
1523
|
+
expect(deriveSubareaPropertyName('invitations')).toBe('invitations');
|
|
1524
|
+
expect(deriveSubareaClientClassName('payments', 'gift-cards')).toBe('PaymentsGiftCardsClient');
|
|
1525
|
+
expect(deriveSubareaPropertyName('gift-cards')).toBe('giftCards');
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it('reads area and subarea from root.meta', () => {
|
|
1529
|
+
const root = opRoot([], 'x.ck', { area: 'identity', subarea: 'invitations' });
|
|
1530
|
+
expect(getAreaSubarea(root)).toEqual({ area: 'identity', subarea: 'invitations' });
|
|
1531
|
+
const noKeys = opRoot([], 'x.ck');
|
|
1532
|
+
expect(getAreaSubarea(noKeys)).toEqual({ area: undefined, subarea: undefined });
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
// ─── generateSdkAggregator — area + subarea wiring ────────────────────────
|
|
1537
|
+
|
|
1538
|
+
describe('generateSdkAggregator — area + subarea', () => {
|
|
1539
|
+
it('emits one <Area>Client per area with subarea property wiring', () => {
|
|
1540
|
+
const out = generateSdkAggregator({
|
|
1541
|
+
topLevelClients: [],
|
|
1542
|
+
areas: [
|
|
1543
|
+
{
|
|
1544
|
+
area: 'identity',
|
|
1545
|
+
inlineFiles: [],
|
|
1546
|
+
subareaClients: [
|
|
1547
|
+
{
|
|
1548
|
+
propertyName: 'invitations',
|
|
1549
|
+
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './identity/invitations.client.js' },
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
propertyName: 'users',
|
|
1553
|
+
client: { className: 'IdentityUsersClient', propertyName: 'users', importPath: './identity/users.client.js' },
|
|
1554
|
+
},
|
|
1555
|
+
],
|
|
1556
|
+
},
|
|
1557
|
+
],
|
|
1558
|
+
});
|
|
1559
|
+
expect(out).toContain("import { IdentityInvitationsClient } from './identity/invitations.client.js'");
|
|
1560
|
+
expect(out).toContain("import { IdentityUsersClient } from './identity/users.client.js'");
|
|
1561
|
+
expect(out).toContain('class IdentityClient {');
|
|
1562
|
+
expect(out).toContain('readonly invitations: IdentityInvitationsClient');
|
|
1563
|
+
expect(out).toContain('readonly users: IdentityUsersClient');
|
|
1564
|
+
expect(out).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
1565
|
+
expect(out).toContain('this.users = new IdentityUsersClient(fetch)');
|
|
1566
|
+
// Sdk wires the area property
|
|
1567
|
+
expect(out).toContain('export class Sdk {');
|
|
1568
|
+
expect(out).toContain('readonly identity: IdentityClient');
|
|
1569
|
+
expect(out).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
it('inlines methods from area-level (no-subarea) files into <Area>Client', () => {
|
|
1573
|
+
const root = opRoot(
|
|
1574
|
+
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200, 'User', 'application/json')] })])],
|
|
1575
|
+
'identity.ck',
|
|
1576
|
+
{ area: 'identity' },
|
|
1577
|
+
);
|
|
1578
|
+
const out = generateSdkAggregator({
|
|
1579
|
+
topLevelClients: [],
|
|
1580
|
+
areas: [
|
|
1581
|
+
{
|
|
1582
|
+
area: 'identity',
|
|
1583
|
+
inlineFiles: [{ root, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } }],
|
|
1584
|
+
subareaClients: [],
|
|
1585
|
+
},
|
|
1586
|
+
],
|
|
1587
|
+
});
|
|
1588
|
+
expect(out).toContain('class IdentityClient {');
|
|
1589
|
+
expect(out).toContain('async getCurrentUser(');
|
|
1590
|
+
// No subarea property declarations on this client
|
|
1591
|
+
expect(out).not.toMatch(/readonly \w+: \w+;[\s\S]*async getCurrentUser/);
|
|
1592
|
+
// The fetch arg is captured as `private fetch` since methods reference `this.fetch`
|
|
1593
|
+
expect(out).toContain('constructor(private fetch: SdkFetch)');
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
it('mixes inline area methods with subarea property wiring on the same area client', () => {
|
|
1597
|
+
const inlineRoot = opRoot(
|
|
1598
|
+
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200, 'User', 'application/json')] })])],
|
|
1599
|
+
'identity.ck',
|
|
1600
|
+
{ area: 'identity' },
|
|
1601
|
+
);
|
|
1602
|
+
const out = generateSdkAggregator({
|
|
1603
|
+
topLevelClients: [],
|
|
1604
|
+
areas: [
|
|
1605
|
+
{
|
|
1606
|
+
area: 'identity',
|
|
1607
|
+
inlineFiles: [{ root: inlineRoot, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } }],
|
|
1608
|
+
subareaClients: [
|
|
1609
|
+
{
|
|
1610
|
+
propertyName: 'invitations',
|
|
1611
|
+
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './identity/invitations.client.js' },
|
|
1612
|
+
},
|
|
1613
|
+
],
|
|
1614
|
+
},
|
|
1615
|
+
],
|
|
1616
|
+
});
|
|
1617
|
+
expect(out).toContain('readonly invitations: IdentityInvitationsClient');
|
|
1618
|
+
expect(out).toContain('async getCurrentUser(');
|
|
1619
|
+
expect(out).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
it('throws on duplicate method names across area-level files in the same area', () => {
|
|
1623
|
+
const a = opRoot(
|
|
1624
|
+
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200, 'User', 'application/json')] })])],
|
|
1625
|
+
'a.ck',
|
|
1626
|
+
{ area: 'identity' },
|
|
1627
|
+
);
|
|
1628
|
+
const b = opRoot(
|
|
1629
|
+
[opRoute('/whoami', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200, 'User', 'application/json')] })])],
|
|
1630
|
+
'b.ck',
|
|
1631
|
+
{ area: 'identity' },
|
|
1632
|
+
);
|
|
1633
|
+
expect(() =>
|
|
1634
|
+
generateSdkAggregator({
|
|
1635
|
+
topLevelClients: [],
|
|
1636
|
+
areas: [
|
|
1637
|
+
{
|
|
1638
|
+
area: 'identity',
|
|
1639
|
+
inlineFiles: [
|
|
1640
|
+
{ root: a, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1641
|
+
{ root: b, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1642
|
+
],
|
|
1643
|
+
subareaClients: [],
|
|
1644
|
+
},
|
|
1645
|
+
],
|
|
1646
|
+
}),
|
|
1647
|
+
).toThrow(/duplicate method 'getCurrentUser' in area 'identity'/);
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
it('mixes area clients with top-level (no-area) clients', () => {
|
|
1651
|
+
const out = generateSdkAggregator({
|
|
1652
|
+
topLevelClients: [{ className: 'WebhooksClient', propertyName: 'webhooks', importPath: './webhooks.client.js' }],
|
|
1653
|
+
areas: [
|
|
1654
|
+
{
|
|
1655
|
+
area: 'payments',
|
|
1656
|
+
inlineFiles: [],
|
|
1657
|
+
subareaClients: [
|
|
1658
|
+
{
|
|
1659
|
+
propertyName: 'transfers',
|
|
1660
|
+
client: { className: 'PaymentsTransfersClient', propertyName: 'transfers', importPath: './payments/transfers.client.js' },
|
|
1661
|
+
},
|
|
1662
|
+
],
|
|
1663
|
+
},
|
|
1664
|
+
],
|
|
1665
|
+
});
|
|
1666
|
+
// Sdk has both an area property AND a top-level property
|
|
1667
|
+
expect(out).toContain('readonly payments: PaymentsClient');
|
|
1668
|
+
expect(out).toContain('readonly webhooks: WebhooksClient');
|
|
1669
|
+
expect(out).toContain('this.payments = new PaymentsClient(sdkFetch)');
|
|
1670
|
+
expect(out).toContain('this.webhooks = new WebhooksClient(sdkFetch)');
|
|
1671
|
+
});
|
|
1672
|
+
});
|
|
@@ -10,6 +10,7 @@ function makeCtx(rootDir = '/project', options: Record<string, unknown> = {}): P
|
|
|
10
10
|
return {
|
|
11
11
|
rootDir,
|
|
12
12
|
options,
|
|
13
|
+
cacheEnabled: true,
|
|
13
14
|
emitFile: (outPath: string, content: string) => {
|
|
14
15
|
emitted.set(outPath, content);
|
|
15
16
|
},
|
|
@@ -63,7 +64,8 @@ describe('createTypescriptPlugin (server)', () => {
|
|
|
63
64
|
]),
|
|
64
65
|
ctx,
|
|
65
66
|
);
|
|
66
|
-
|
|
67
|
+
const routeFiles = [...ctx.emitted.keys()].filter(p => p.endsWith('.router.ts'));
|
|
68
|
+
expect(routeFiles.length).toBe(2);
|
|
67
69
|
});
|
|
68
70
|
|
|
69
71
|
it('emits type files alongside routes when output.types is configured', async () => {
|
|
@@ -190,3 +192,101 @@ describe('createTypescriptPlugin (server)', () => {
|
|
|
190
192
|
});
|
|
191
193
|
});
|
|
192
194
|
});
|
|
195
|
+
|
|
196
|
+
// ─── SDK pipeline: area / subarea grouping ─────────────────────────────────
|
|
197
|
+
|
|
198
|
+
describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
199
|
+
function findEmitted(emitted: Map<string, string>, suffix: string): string | undefined {
|
|
200
|
+
for (const [path, content] of emitted) {
|
|
201
|
+
if (path.endsWith(suffix)) return content;
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
it('emits a leaf <Area><Subarea>Client and nests it under <Area>Client in sdk.ts', async () => {
|
|
207
|
+
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{subarea}.client.ts' } } }, '/project');
|
|
208
|
+
const ctx = makeCtx('/project');
|
|
209
|
+
const root = opRoot(
|
|
210
|
+
[opRoute('/identity/invitations', [opOperation('post', { sdk: 'createInvitation', responses: [opResponse(201)] })])],
|
|
211
|
+
'/project/contracts/identity-invitations.ck',
|
|
212
|
+
{ area: 'identity', subarea: 'invitations' },
|
|
213
|
+
);
|
|
214
|
+
await plugin.generateTargets!(inputs([root]), ctx);
|
|
215
|
+
|
|
216
|
+
// Leaf client emitted at the {area}/{subarea}.client.ts path
|
|
217
|
+
const leaf = findEmitted(ctx.emitted, 'identity/invitations.client.ts');
|
|
218
|
+
expect(leaf).toBeDefined();
|
|
219
|
+
expect(leaf!).toContain('export class IdentityInvitationsClient');
|
|
220
|
+
expect(leaf!).toContain('async createInvitation');
|
|
221
|
+
|
|
222
|
+
// Aggregator declares IdentityClient + Sdk wiring
|
|
223
|
+
const sdk = findEmitted(ctx.emitted, '/sdk.ts');
|
|
224
|
+
expect(sdk).toBeDefined();
|
|
225
|
+
expect(sdk!).toContain('class IdentityClient {');
|
|
226
|
+
expect(sdk!).toContain('readonly invitations: IdentityInvitationsClient');
|
|
227
|
+
expect(sdk!).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
228
|
+
expect(sdk!).toContain('export class Sdk {');
|
|
229
|
+
expect(sdk!).toContain('readonly identity: IdentityClient');
|
|
230
|
+
expect(sdk!).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('does NOT emit a standalone client file for an area-level (no-subarea) input — methods inline into sdk.ts', async () => {
|
|
234
|
+
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project');
|
|
235
|
+
const ctx = makeCtx('/project');
|
|
236
|
+
const root = opRoot(
|
|
237
|
+
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200)] })])],
|
|
238
|
+
'/project/contracts/identity-me.ck',
|
|
239
|
+
{ area: 'identity' },
|
|
240
|
+
);
|
|
241
|
+
await plugin.generateTargets!(inputs([root]), ctx);
|
|
242
|
+
|
|
243
|
+
// No per-file leaf for the area-level input
|
|
244
|
+
for (const path of ctx.emitted.keys()) {
|
|
245
|
+
expect(path).not.toMatch(/identity[/-]me\.client\.ts$/);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Methods land directly on IdentityClient inside sdk.ts
|
|
249
|
+
const sdk = findEmitted(ctx.emitted, '/sdk.ts');
|
|
250
|
+
expect(sdk).toBeDefined();
|
|
251
|
+
expect(sdk!).toContain('class IdentityClient {');
|
|
252
|
+
expect(sdk!).toContain('async getCurrentUser');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('mixes area-level inline methods with subarea property wiring on the same area client', async () => {
|
|
256
|
+
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project');
|
|
257
|
+
const ctx = makeCtx('/project');
|
|
258
|
+
const me = opRoot(
|
|
259
|
+
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200)] })])],
|
|
260
|
+
'/project/contracts/identity-me.ck',
|
|
261
|
+
{ area: 'identity' },
|
|
262
|
+
);
|
|
263
|
+
const invitations = opRoot(
|
|
264
|
+
[opRoute('/identity/invitations', [opOperation('post', { sdk: 'createInvitation', responses: [opResponse(201)] })])],
|
|
265
|
+
'/project/contracts/identity-invitations.ck',
|
|
266
|
+
{ area: 'identity', subarea: 'invitations' },
|
|
267
|
+
);
|
|
268
|
+
await plugin.generateTargets!(inputs([me, invitations]), ctx);
|
|
269
|
+
|
|
270
|
+
const sdk = findEmitted(ctx.emitted, '/sdk.ts')!;
|
|
271
|
+
expect(sdk).toContain('class IdentityClient {');
|
|
272
|
+
expect(sdk).toContain('readonly invitations: IdentityInvitationsClient');
|
|
273
|
+
expect(sdk).toContain('async getCurrentUser');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('preserves legacy flat wiring for files with no area', async () => {
|
|
277
|
+
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{filename}.client.ts' } } }, '/project');
|
|
278
|
+
const ctx = makeCtx('/project');
|
|
279
|
+
const root = opRoot(
|
|
280
|
+
[opRoute('/webhooks', [opOperation('post', { sdk: 'sendWebhook', responses: [opResponse(202)] })])],
|
|
281
|
+
'/project/contracts/webhooks.ck',
|
|
282
|
+
);
|
|
283
|
+
await plugin.generateTargets!(inputs([root]), ctx);
|
|
284
|
+
|
|
285
|
+
const sdk = findEmitted(ctx.emitted, '/sdk.ts')!;
|
|
286
|
+
// No <Area>Client wrapper; flat property on Sdk
|
|
287
|
+
expect(sdk).toContain('export class Sdk {');
|
|
288
|
+
expect(sdk).toContain('readonly webhooks: WebhooksClient');
|
|
289
|
+
expect(sdk).toContain('this.webhooks = new WebhooksClient(sdkFetch)');
|
|
290
|
+
expect(sdk).not.toMatch(/class \w+Client \{/m);
|
|
291
|
+
});
|
|
292
|
+
});
|