diff --git a/src/modules/image/index.ts b/src/modules/image/index.ts index 2275cbeba50..6b0a70171f8 100644 --- a/src/modules/image/index.ts +++ b/src/modules/image/index.ts @@ -1,6 +1,7 @@ import { toBase64 } from '../../internal/base64'; import { deprecated } from '../../internal/deprecated'; import { ModuleBase } from '../../internal/module-base'; +import type { SexType } from '../person'; /** * Module to generate images. @@ -11,7 +12,7 @@ import { ModuleBase } from '../../internal/module-base'; * * For a random placeholder image containing only solid color and text, use [`urlPlaceholder()`](https://fakerjs.dev/api/image.html#urlplaceholder) (uses a third-party service) or [`dataUri()`](https://fakerjs.dev/api/image.html#datauri) (returns a SVG string). * - * For a random user avatar image, use [`avatar()`](https://fakerjs.dev/api/image.html#avatar). + * For a random user avatar image, use [`avatar()`](https://fakerjs.dev/api/image.html#avatar), or [`personPortrait()`](https://fakerjs.dev/api/image.html#personportrait) which has more control over the size and sex of the person. * * If you need more control over the content of the images, you can pass a `category` parameter e.g. `'cat'` or `'nature'` to [`urlLoremFlickr()`](https://fakerjs.dev/api/image.html#urlloremflickr) or simply use [`faker.helpers.arrayElement()`](https://fakerjs.dev/api/helpers.html#arrayelement) with your own array of image URLs. */ @@ -27,7 +28,11 @@ export class ImageModule extends ModuleBase { */ avatar(): string { // Add new avatar providers here, when adding a new one. - return this.avatarGitHub(); + const avatarMethod = this.faker.helpers.arrayElement([ + this.personPortrait, + this.avatarGitHub, + ]); + return avatarMethod(); } /** @@ -45,6 +50,45 @@ export class ImageModule extends ModuleBase { )}`; } + /** + * Generates a random square portrait (avatar) of a person. + * These are static images of fictional people created by an AI, Stable Diffusion 3. + * The image URLs are served via the JSDelivr CDN and subject to their [terms of use](https://www.jsdelivr.com/terms). + * + * @param options Options for generating an AI avatar. + * @param options.sex The sex of the person for the avatar. Can be `'female'` or `'male'`. If not provided, defaults to a random selection. + * @param options.size The size of the image. Can be `512`, `256`, `128`, `64` or `32`. If not provided, defaults to `512`. + * + * @example + * faker.image.personPortrait() // 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/57.jpg' + * faker.image.personPortrait({ sex: 'male', size: '128' }) // 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/128/27.jpg' + * + * @since 9.5.0 + */ + personPortrait( + options: { + /** + * The sex of the person for the avatar. + * Can be `'female'` or `'male'`. + * + * @default faker.person.sexType() + */ + sex?: SexType; + /** + * The size of the image. + * Can be `512`, `256`, `128`, `64` or `32`. + * + * @default 512 + */ + size?: 512 | 256 | 128 | 64 | 32; + } = {} + ): string { + const { sex = this.faker.person.sexType(), size = 512 } = options; + const baseURL = + 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait'; + return `${baseURL}/${sex}/${size}/${this.faker.number.int({ min: 0, max: 99 })}.jpg`; + } + /** * Generates a random avatar from `https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar`. * @@ -59,7 +103,7 @@ export class ImageModule extends ModuleBase { avatarLegacy(): string { deprecated({ deprecated: 'faker.image.avatarLegacy()', - proposed: 'faker.image.avatar()', + proposed: 'faker.image.avatar() or faker.image.personPortrait()', since: '9.0.2', until: '10.0.0', }); diff --git a/test/integration/modules/image.spec.ts b/test/integration/modules/image.spec.ts index 611317c610b..5ec9e34bd33 100644 --- a/test/integration/modules/image.spec.ts +++ b/test/integration/modules/image.spec.ts @@ -74,6 +74,13 @@ describe('image', () => { }); }); + describe('personPortrait', () => { + it('should return a random asset url', async () => { + const actual = faker.image.personPortrait(); + await assertWorkingUrl(actual); + }); + }); + describe('url', () => { it('should return a random image url', async () => { const actual = faker.image.url(); diff --git a/test/modules/__snapshots__/image.spec.ts.snap b/test/modules/__snapshots__/image.spec.ts.snap index bccdada46d1..2bcbe1a3340 100644 --- a/test/modules/__snapshots__/image.spec.ts.snap +++ b/test/modules/__snapshots__/image.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`image > 42 > avatar 1`] = `"https://avatars.githubusercontent.com/u/37454012"`; +exports[`image > 42 > avatar 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/512/73.jpg"`; exports[`image > 42 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/37454012"`; @@ -22,6 +22,14 @@ exports[`image > 42 > dataUri > with width 1`] = `"data:image/svg+xml;base64,PHN exports[`image > 42 > dataUri > with width and height 1`] = `"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20baseProfile%3D%22full%22%20width%3D%22128%22%20height%3D%22128%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%238ead33%22%2F%3E%3Ctext%20x%3D%2264%22%20y%3D%2264%22%20font-size%3D%2220%22%20alignment-baseline%3D%22middle%22%20text-anchor%3D%22middle%22%20fill%3D%22white%22%3E128x128%3C%2Ftext%3E%3C%2Fsvg%3E"`; +exports[`image > 42 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/95.jpg"`; + +exports[`image > 42 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/37.jpg"`; + +exports[`image > 42 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/37.jpg"`; + +exports[`image > 42 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/128/95.jpg"`; + exports[`image > 42 > url > noArgs 1`] = `"https://picsum.photos/seed/993RBH1Y/1498/3802"`; exports[`image > 42 > url > with height 1`] = `"https://picsum.photos/seed/B993RBH1Y/1498/128"`; @@ -76,7 +84,7 @@ exports[`image > 42 > urlPlaceholder > with width 1`] = `"https://via.placeholde exports[`image > 42 > urlPlaceholder > with width and height 1`] = `"https://via.placeholder.com/128x128/8ead33/1ddf0f.webp?text=benevolentia%20attonbitus%20auctus"`; -exports[`image > 1211 > avatar 1`] = `"https://avatars.githubusercontent.com/u/92852016"`; +exports[`image > 1211 > avatar 1`] = `"https://avatars.githubusercontent.com/u/89347165"`; exports[`image > 1211 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/92852016"`; @@ -98,6 +106,14 @@ exports[`image > 1211 > dataUri > with width 1`] = `"data:image/svg+xml;charset= exports[`image > 1211 > dataUri > with width and height 1`] = `"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgYmFzZVByb2ZpbGU9ImZ1bGwiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWQ0ZmVmIi8+PHRleHQgeD0iNjQiIHk9IjY0IiBmb250LXNpemU9IjIwIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0id2hpdGUiPjEyOHgxMjg8L3RleHQ+PC9zdmc+"`; +exports[`image > 1211 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/512/89.jpg"`; + +exports[`image > 1211 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/92.jpg"`; + +exports[`image > 1211 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/92.jpg"`; + +exports[`image > 1211 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/128/89.jpg"`; + exports[`image > 1211 > url > noArgs 1`] = `"https://loremflickr.com/3714/3573?lock=8982492793493979"`; exports[`image > 1211 > url > with height 1`] = `"https://picsum.photos/seed/ZFGLlH/3714/128"`; @@ -152,7 +168,7 @@ exports[`image > 1211 > urlPlaceholder > with width 1`] = `"https://via.placehol exports[`image > 1211 > urlPlaceholder > with width and height 1`] = `"https://via.placeholder.com/128x128/ed4fef/a7fbae.webp?text=dapifer%20usque%20unde"`; -exports[`image > 1337 > avatar 1`] = `"https://avatars.githubusercontent.com/u/26202467"`; +exports[`image > 1337 > avatar 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/27.jpg"`; exports[`image > 1337 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/26202467"`; @@ -174,6 +190,14 @@ exports[`image > 1337 > dataUri > with width 1`] = `"data:image/svg+xml;base64,P exports[`image > 1337 > dataUri > with width and height 1`] = `"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20baseProfile%3D%22full%22%20width%3D%22128%22%20height%3D%22128%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%23536a7b%22%2F%3E%3Ctext%20x%3D%2264%22%20y%3D%2264%22%20font-size%3D%2220%22%20alignment-baseline%3D%22middle%22%20text-anchor%3D%22middle%22%20fill%3D%22white%22%3E128x128%3C%2Ftext%3E%3C%2Fsvg%3E"`; +exports[`image > 1337 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/15.jpg"`; + +exports[`image > 1337 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/26.jpg"`; + +exports[`image > 1337 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/26.jpg"`; + +exports[`image > 1337 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/128/15.jpg"`; + exports[`image > 1337 > url > noArgs 1`] = `"https://loremflickr.com/1048/635?lock=4137158724208997"`; exports[`image > 1337 > url > with height 1`] = `"https://loremflickr.com/1048/128?lock=2505140979113303"`; diff --git a/test/modules/image.spec.ts b/test/modules/image.spec.ts index 2f9b0f024a8..0d494cb4a42 100644 --- a/test/modules/image.spec.ts +++ b/test/modules/image.spec.ts @@ -108,6 +108,13 @@ describe('image', () => { type: 'svg-uri', }); }); + + t.describe('personPortrait', (t) => { + t.it('noArgs') + .it('with sex', { sex: 'female' }) + .it('with size', { size: 128 }) + .it('with sex and size', { sex: 'male', size: 256 }); + }); }); describe('avatar', () => { @@ -144,6 +151,28 @@ describe('image', () => { }); }); + describe('personPortrait', () => { + it('should return a random avatar url from AI', () => { + const imageUrl = faker.image.personPortrait(); + + expect(imageUrl).toBeTypeOf('string'); + expect(imageUrl).toMatch( + /^https:\/\/cdn\.jsdelivr\.net\/gh\/faker-js\/assets-person-portrait\/(female|male)\/512\/\d{1,2}\.jpg$/ + ); + expect(() => new URL(imageUrl)).not.toThrow(); + }); + + it('should return a random avatar url from AI with fixed size and sex', () => { + const imageUrl = faker.image.personPortrait({ sex: 'male', size: 128 }); + + expect(imageUrl).toBeTypeOf('string'); + expect(imageUrl).toMatch( + /^https:\/\/cdn\.jsdelivr\.net\/gh\/faker-js\/assets-person-portrait\/male\/128\/\d{1,2}\.jpg$/ + ); + expect(() => new URL(imageUrl)).not.toThrow(); + }); + }); + describe('url', () => { it('should return a random image url', () => { const actual = faker.image.url(); diff --git a/test/scripts/apidocs/__snapshots__/verify-jsdoc-tags.spec.ts.snap b/test/scripts/apidocs/__snapshots__/verify-jsdoc-tags.spec.ts.snap index c13b2811a53..07983094b8c 100644 --- a/test/scripts/apidocs/__snapshots__/verify-jsdoc-tags.spec.ts.snap +++ b/test/scripts/apidocs/__snapshots__/verify-jsdoc-tags.spec.ts.snap @@ -243,6 +243,7 @@ exports[`check docs completeness > all modules and methods are present 1`] = ` "avatarGitHub", "avatarLegacy", "dataUri", + "personPortrait", "url", "urlLoremFlickr", "urlPicsumPhotos", diff --git a/test/scripts/apidocs/verify-jsdoc-tags.spec.ts b/test/scripts/apidocs/verify-jsdoc-tags.spec.ts index 703e42cf37b..dd77620d504 100644 --- a/test/scripts/apidocs/verify-jsdoc-tags.spec.ts +++ b/test/scripts/apidocs/verify-jsdoc-tags.spec.ts @@ -147,7 +147,7 @@ ${examples}`; if (!examples.includes('import ')) { const imports = [ // collect the imports for the various locales e.g. fakerDE_CH - ...new Set(examples.match(/(? 0) {