diff --git a/src/api/extract.spec.ts b/src/api/extract.spec.ts index a8a485763c..47ee3b1437 100644 --- a/src/api/extract.spec.ts +++ b/src/api/extract.spec.ts @@ -152,6 +152,28 @@ describe('$.extract', () => { ).toStrictEqual({ red: 'red=Four' }); }); + it('should correctly type check custom extraction functions returning non-string values', () => { + const $ = load(fixtures.eleven); + const $root = $.root(); + + expectTypeOf( + $root.extract({ + red: { + selector: '.red', + value: (el) => $(el).text().length, + }, + }), + ).toEqualTypeOf<{ red: number | undefined }>(); + expect( + $root.extract({ + red: { + selector: '.red', + value: (el) => $(el).text().length, + }, + }), + ).toStrictEqual({ red: 4 }); + }); + it('should extract multiple values using custom extraction functions', () => { const $ = load(fixtures.eleven); const $root = $.root(); @@ -215,6 +237,44 @@ describe('$.extract', () => { }); }); + it('should correctly type check nested objects returning non-string values', () => { + const $ = load(fixtures.eleven); + const $root = $.root(); + + expectTypeOf( + $root.extract({ + section: { + selector: 'ul:nth(1)', + value: { + red: { + selector: '.red', + value: (el) => $(el).text().length, + }, + }, + }, + }), + ).toEqualTypeOf<{ + section: { red: number | undefined } | undefined; + }>(); + expect( + $root.extract({ + section: { + selector: 'ul:nth(1)', + value: { + red: { + selector: '.red', + value: (el) => $(el).text().length, + }, + }, + }, + }), + ).toStrictEqual({ + section: { + red: 4, + }, + }); + }); + it('should handle missing href properties without errors (#4239)', () => { const $ = load(fixtures.eleven); expect<{ links: string[] }>( diff --git a/src/api/extract.ts b/src/api/extract.ts index 8bb0b62cb6..238c865df9 100644 --- a/src/api/extract.ts +++ b/src/api/extract.ts @@ -18,22 +18,24 @@ type ExtractValue = string | ExtractDescriptor | [string | ExtractDescriptor]; export type ExtractMap = Record; -type ExtractedValue = V extends [ +type ExtractedValue = V extends [ string | ExtractDescriptor, ] - ? NonNullable>[] + ? NonNullable>[] : V extends string ? string | undefined : V extends ExtractDescriptor - ? V['value'] extends ExtractMap - ? ExtractedMap | undefined - : V['value'] extends ExtractDescriptorFn - ? ReturnType | undefined - : ReturnType | undefined + ? V['value'] extends infer U + ? U extends ExtractMap + ? ExtractedMap | undefined + : U extends ExtractDescriptorFn + ? ReturnType | undefined + : ReturnType | undefined + : never : never; export type ExtractedMap = { - [key in keyof M]: ExtractedValue; + [key in keyof M]: ExtractedValue; }; function getExtractDescr(