Skip to content

Commit 281d249

Browse files
authored
Merge branch 'next' into test-set-summary-for-local
2 parents 9537426 + e28273f commit 281d249

File tree

12 files changed

+490
-436
lines changed

12 files changed

+490
-436
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
44

5+
## [9.5.0](https://github.com/faker-js/faker/compare/v9.4.0...v9.5.0) (2025-02-10)
6+
7+
8+
### Features
9+
10+
* **image:** add AI-generated avatars ([#3126](https://github.com/faker-js/faker/issues/3126)) ([9e13953](https://github.com/faker-js/faker/commit/9e1395380cf9baf9f0350c43cbbc303430e77dfb))
11+
512
## [9.4.0](https://github.com/faker-js/faker/compare/v9.3.0...v9.4.0) (2025-01-15)
613

714

docs/guide/usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ const randomEmail = faker.internet.email(); // [email protected]
7979
```
8080

8181
::: info Note
82-
It is highly recommended to use version tags when importing libraries in Deno, e.g: `import { faker } from "https://esm.sh/@faker-js/faker@v9.4.0"`.
82+
It is highly recommended to use version tags when importing libraries in Deno, e.g: `import { faker } from "https://esm.sh/@faker-js/faker@v9.5.0"`.
8383
:::
8484

8585
### Alternative CDN links

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@faker-js/faker",
3-
"version": "9.4.0",
3+
"version": "9.5.0",
44
"description": "Generate massive amounts of fake contextual data",
55
"scripts": {
66
"clean": "rimraf coverage .eslintcache dist docs/.vitepress/cache docs/.vitepress/dist node_modules",

pnpm-lock.yaml

Lines changed: 313 additions & 376 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/modules/image/index.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { toBase64 } from '../../internal/base64';
22
import { deprecated } from '../../internal/deprecated';
33
import { ModuleBase } from '../../internal/module-base';
4+
import type { SexType } from '../person';
45

56
/**
67
* Module to generate images.
@@ -11,7 +12,7 @@ import { ModuleBase } from '../../internal/module-base';
1112
*
1213
* 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).
1314
*
14-
* For a random user avatar image, use [`avatar()`](https://fakerjs.dev/api/image.html#avatar).
15+
* 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.
1516
*
1617
* 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.
1718
*/
@@ -27,7 +28,11 @@ export class ImageModule extends ModuleBase {
2728
*/
2829
avatar(): string {
2930
// Add new avatar providers here, when adding a new one.
30-
return this.avatarGitHub();
31+
const avatarMethod = this.faker.helpers.arrayElement([
32+
this.personPortrait,
33+
this.avatarGitHub,
34+
]);
35+
return avatarMethod();
3136
}
3237

3338
/**
@@ -45,6 +50,45 @@ export class ImageModule extends ModuleBase {
4550
)}`;
4651
}
4752

53+
/**
54+
* Generates a random square portrait (avatar) of a person.
55+
* These are static images of fictional people created by an AI, Stable Diffusion 3.
56+
* The image URLs are served via the JSDelivr CDN and subject to their [terms of use](https://www.jsdelivr.com/terms).
57+
*
58+
* @param options Options for generating an AI avatar.
59+
* @param options.sex The sex of the person for the avatar. Can be `'female'` or `'male'`. If not provided, defaults to a random selection.
60+
* @param options.size The size of the image. Can be `512`, `256`, `128`, `64` or `32`. If not provided, defaults to `512`.
61+
*
62+
* @example
63+
* faker.image.personPortrait() // 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/57.jpg'
64+
* faker.image.personPortrait({ sex: 'male', size: '128' }) // 'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/128/27.jpg'
65+
*
66+
* @since 9.5.0
67+
*/
68+
personPortrait(
69+
options: {
70+
/**
71+
* The sex of the person for the avatar.
72+
* Can be `'female'` or `'male'`.
73+
*
74+
* @default faker.person.sexType()
75+
*/
76+
sex?: SexType;
77+
/**
78+
* The size of the image.
79+
* Can be `512`, `256`, `128`, `64` or `32`.
80+
*
81+
* @default 512
82+
*/
83+
size?: 512 | 256 | 128 | 64 | 32;
84+
} = {}
85+
): string {
86+
const { sex = this.faker.person.sexType(), size = 512 } = options;
87+
const baseURL =
88+
'https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait';
89+
return `${baseURL}/${sex}/${size}/${this.faker.number.int({ min: 0, max: 99 })}.jpg`;
90+
}
91+
4892
/**
4993
* Generates a random avatar from `https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar`.
5094
*
@@ -59,7 +103,7 @@ export class ImageModule extends ModuleBase {
59103
avatarLegacy(): string {
60104
deprecated({
61105
deprecated: 'faker.image.avatarLegacy()',
62-
proposed: 'faker.image.avatar()',
106+
proposed: 'faker.image.avatar() or faker.image.personPortrait()',
63107
since: '9.0.2',
64108
until: '10.0.0',
65109
});

test/integration/modules/image.spec.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ describe('image', () => {
7474
});
7575
});
7676

77+
describe('personPortrait', () => {
78+
it('should return a random asset url', async () => {
79+
const actual = faker.image.personPortrait();
80+
await assertWorkingUrl(actual);
81+
});
82+
});
83+
7784
describe('url', () => {
7885
it('should return a random image url', async () => {
7986
const actual = faker.image.url();
@@ -109,12 +116,4 @@ describe('image', () => {
109116
await assertWorkingUrl(actual);
110117
});
111118
});
112-
113-
describe('urlPlaceholder', () => {
114-
it('should return a random image url from Placeholder', async () => {
115-
// eslint-disable-next-line @typescript-eslint/no-deprecated
116-
const actual = faker.image.urlPlaceholder();
117-
await assertWorkingUrl(actual);
118-
});
119-
});
120119
});

test/modules/__snapshots__/image.spec.ts.snap

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`image > 42 > avatar 1`] = `"https://avatars.githubusercontent.com/u/37454012"`;
3+
exports[`image > 42 > avatar 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/512/73.jpg"`;
44

55
exports[`image > 42 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/37454012"`;
66

@@ -22,6 +22,14 @@ exports[`image > 42 > dataUri > with width 1`] = `"data:image/svg+xml;base64,PHN
2222

2323
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"`;
2424

25+
exports[`image > 42 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/95.jpg"`;
26+
27+
exports[`image > 42 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/37.jpg"`;
28+
29+
exports[`image > 42 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/37.jpg"`;
30+
31+
exports[`image > 42 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/128/95.jpg"`;
32+
2533
exports[`image > 42 > url > noArgs 1`] = `"https://picsum.photos/seed/993RBH1Y/1498/3802"`;
2634

2735
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
7684

7785
exports[`image > 42 > urlPlaceholder > with width and height 1`] = `"https://via.placeholder.com/128x128/8ead33/1ddf0f.webp?text=benevolentia%20attonbitus%20auctus"`;
7886

79-
exports[`image > 1211 > avatar 1`] = `"https://avatars.githubusercontent.com/u/92852016"`;
87+
exports[`image > 1211 > avatar 1`] = `"https://avatars.githubusercontent.com/u/89347165"`;
8088

8189
exports[`image > 1211 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/92852016"`;
8290

@@ -98,6 +106,14 @@ exports[`image > 1211 > dataUri > with width 1`] = `"data:image/svg+xml;charset=
98106

99107
exports[`image > 1211 > dataUri > with width and height 1`] = `"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgYmFzZVByb2ZpbGU9ImZ1bGwiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWQ0ZmVmIi8+PHRleHQgeD0iNjQiIHk9IjY0IiBmb250LXNpemU9IjIwIiBhbGlnbm1lbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0id2hpdGUiPjEyOHgxMjg8L3RleHQ+PC9zdmc+"`;
100108

109+
exports[`image > 1211 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/512/89.jpg"`;
110+
111+
exports[`image > 1211 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/92.jpg"`;
112+
113+
exports[`image > 1211 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/92.jpg"`;
114+
115+
exports[`image > 1211 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/128/89.jpg"`;
116+
101117
exports[`image > 1211 > url > noArgs 1`] = `"https://loremflickr.com/3714/3573?lock=8982492793493979"`;
102118

103119
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
152168

153169
exports[`image > 1211 > urlPlaceholder > with width and height 1`] = `"https://via.placeholder.com/128x128/ed4fef/a7fbae.webp?text=dapifer%20usque%20unde"`;
154170

155-
exports[`image > 1337 > avatar 1`] = `"https://avatars.githubusercontent.com/u/26202467"`;
171+
exports[`image > 1337 > avatar 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/27.jpg"`;
156172

157173
exports[`image > 1337 > avatarGitHub 1`] = `"https://avatars.githubusercontent.com/u/26202467"`;
158174

@@ -174,6 +190,14 @@ exports[`image > 1337 > dataUri > with width 1`] = `"data:image/svg+xml;base64,P
174190

175191
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"`;
176192

193+
exports[`image > 1337 > personPortrait > noArgs 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/15.jpg"`;
194+
195+
exports[`image > 1337 > personPortrait > with sex 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/512/26.jpg"`;
196+
197+
exports[`image > 1337 > personPortrait > with sex and size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/male/256/26.jpg"`;
198+
199+
exports[`image > 1337 > personPortrait > with size 1`] = `"https://cdn.jsdelivr.net/gh/faker-js/assets-person-portrait/female/128/15.jpg"`;
200+
177201
exports[`image > 1337 > url > noArgs 1`] = `"https://loremflickr.com/1048/635?lock=4137158724208997"`;
178202

179203
exports[`image > 1337 > url > with height 1`] = `"https://loremflickr.com/1048/128?lock=2505140979113303"`;

test/modules/image.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ describe('image', () => {
108108
type: 'svg-uri',
109109
});
110110
});
111+
112+
t.describe('personPortrait', (t) => {
113+
t.it('noArgs')
114+
.it('with sex', { sex: 'female' })
115+
.it('with size', { size: 128 })
116+
.it('with sex and size', { sex: 'male', size: 256 });
117+
});
111118
});
112119

113120
describe('avatar', () => {
@@ -144,6 +151,28 @@ describe('image', () => {
144151
});
145152
});
146153

154+
describe('personPortrait', () => {
155+
it('should return a random avatar url from AI', () => {
156+
const imageUrl = faker.image.personPortrait();
157+
158+
expect(imageUrl).toBeTypeOf('string');
159+
expect(imageUrl).toMatch(
160+
/^https:\/\/cdn\.jsdelivr\.net\/gh\/faker-js\/assets-person-portrait\/(female|male)\/512\/\d{1,2}\.jpg$/
161+
);
162+
expect(() => new URL(imageUrl)).not.toThrow();
163+
});
164+
165+
it('should return a random avatar url from AI with fixed size and sex', () => {
166+
const imageUrl = faker.image.personPortrait({ sex: 'male', size: 128 });
167+
168+
expect(imageUrl).toBeTypeOf('string');
169+
expect(imageUrl).toMatch(
170+
/^https:\/\/cdn\.jsdelivr\.net\/gh\/faker-js\/assets-person-portrait\/male\/128\/\d{1,2}\.jpg$/
171+
);
172+
expect(() => new URL(imageUrl)).not.toThrow();
173+
});
174+
});
175+
147176
describe('url', () => {
148177
it('should return a random image url', () => {
149178
const actual = faker.image.url();

test/modules/number.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,8 +710,7 @@ describe('number', () => {
710710
expect(actual).toBe(0);
711711
});
712712

713-
// TODO @ST-DDT 2023-10-12: This requires a randomizer with 53 bits of precision
714-
it.todo('should be able to return MAX_SAFE_INTEGER', () => {
713+
it('should be able to return MAX_SAFE_INTEGER', () => {
715714
randomizer.next = () => MERSENNE_MAX_VALUE;
716715
const actual = customFaker.number.int();
717716
expect(actual).toBe(Number.MAX_SAFE_INTEGER);

test/scripts/apidocs/__snapshots__/verify-jsdoc-tags.spec.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ exports[`check docs completeness > all modules and methods are present 1`] = `
243243
"avatarGitHub",
244244
"avatarLegacy",
245245
"dataUri",
246+
"personPortrait",
246247
"url",
247248
"urlLoremFlickr",
248249
"urlPicsumPhotos",

test/scripts/apidocs/verify-jsdoc-tags.spec.ts

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ ${examples}`;
147147
if (!examples.includes('import ')) {
148148
const imports = [
149149
// collect the imports for the various locales e.g. fakerDE_CH
150-
...new Set(examples.match(/(?<!\.)faker[^.]*(?=\.)/g)),
150+
...new Set(examples.match(/(?<!\.)faker[^.-]*(?=\.)/g)),
151151
];
152152

153153
if (imports.length > 0) {
@@ -164,54 +164,68 @@ ${examples}`;
164164
assertDescription(signature.description);
165165
});
166166

167-
it('verify @example tag', { timeout: 30000 }, async () => {
168-
const examples = signature.examples.join('\n');
169-
170-
expect(
171-
examples,
172-
`${moduleName}.${methodName} to have examples`
173-
).not.toBe('');
174-
175-
// Grab path to example file
176-
const path = resolvePathToMethodFile(
177-
moduleName,
178-
methodName,
179-
signatureIndex
180-
);
167+
it(
168+
'verify @example tag',
169+
{
170+
retry: 3,
171+
timeout: 30000,
172+
},
173+
async () => {
174+
const examples = signature.examples.join('\n');
175+
176+
expect(
177+
examples,
178+
`${moduleName}.${methodName} to have examples`
179+
).not.toBe('');
180+
181+
// Grab path to example file
182+
const path = resolvePathToMethodFile(
183+
moduleName,
184+
methodName,
185+
signatureIndex
186+
);
181187

182-
// Executing the examples should not throw
183-
await expect(
184-
import(`${path}?scope=example`),
185-
examples
186-
).resolves.toBeDefined();
187-
});
188+
// Executing the examples should not throw
189+
await expect(
190+
import(`${path}?scope=example`),
191+
examples
192+
).resolves.toBeDefined();
193+
}
194+
);
188195

189196
// This only checks whether the whole method is deprecated or not
190197
// It does not check whether the method is deprecated for a specific set of arguments
191-
it('verify @deprecated tag', { timeout: 30000 }, async () => {
192-
// Grab path to example file
193-
const path = resolvePathToMethodFile(
194-
moduleName,
195-
methodName,
196-
signatureIndex
197-
);
198+
it(
199+
'verify @deprecated tag',
200+
{
201+
retry: 3,
202+
timeout: 30000,
203+
},
204+
async () => {
205+
// Grab path to example file
206+
const path = resolvePathToMethodFile(
207+
moduleName,
208+
methodName,
209+
signatureIndex
210+
);
198211

199-
const consoleWarnSpy = vi.spyOn(console, 'warn');
212+
const consoleWarnSpy = vi.spyOn(console, 'warn');
200213

201-
// Run the examples
202-
await import(`${path}?scope=deprecated`);
214+
// Run the examples
215+
await import(`${path}?scope=deprecated`);
203216

204-
// Verify that deprecated methods log a warning
205-
const { deprecated } = signature;
206-
if (deprecated == null) {
207-
expect(consoleWarnSpy).not.toHaveBeenCalled();
208-
} else {
209-
expect(consoleWarnSpy).toHaveBeenCalled();
210-
expect(deprecated).not.toBe('');
211-
}
217+
// Verify that deprecated methods log a warning
218+
const { deprecated } = signature;
219+
if (deprecated == null) {
220+
expect(consoleWarnSpy).not.toHaveBeenCalled();
221+
} else {
222+
expect(consoleWarnSpy).toHaveBeenCalled();
223+
expect(deprecated).not.toBe('');
224+
}
212225

213-
consoleWarnSpy.mockRestore();
214-
});
226+
consoleWarnSpy.mockRestore();
227+
}
228+
);
215229

216230
describe.each(signature.parameters.map((p) => [p.name, p]))(
217231
'%s',

test/utils/mersenne-test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ export const TWISTER_53CO_MAX_VALUE = 0.9999999999999999;
1414
/**
1515
* The maximum value that can be returned by `next()`.
1616
*/
17-
export const MERSENNE_MAX_VALUE = TWISTER_32CO_MAX_VALUE;
17+
export const MERSENNE_MAX_VALUE = TWISTER_53CO_MAX_VALUE;

0 commit comments

Comments
 (0)